Утилита uniset-codegen предназначена для генерирования "скелета" процесса управления на основе заданного xml-описания. В скелет процесса включается "рутинный" код по получению и проверке индетификаторов, переменных, обработке сообщений и ряд других вспомогательных функций. Помимо этого определяются базовые виртуальные функции, участвующие в процессе.
Процесс можно сгенерировать по нескольким шаблонам
- Заметки:
- Генерирование на основе отдельного xml-файла удобно если вам необходимо создавать несколько объектов вашего класса, отличающихся только набором датчиков.
Генерирование на основе кофигурационного файла проекта (шаблон 'Alone') удобно использовать когда у вас планируется один единственный объект вашего класса. Т.к. в данном случае привязка производиться непосредственно к конкретным датчикам в конфигурационном файле.
Генерируемый процесс функционирует по следующему обобщённому алгоритму:
- Обновление входов (в случае работы на основе заказа датчиков, этот шаг отсутствует)
- проверка срабатывания внутренних таймеров
- Обработка сообщений в очереди сообщений
- Выполенение шага алгоритма (функция step)
- обновление датчика "сердцебиения" /см. Слежение за "живостью" объектов ("сердцебиение") /
- Обновление выходов
Помимо этого обрабатывается специальный режим: Специальный режим "тест" (TestMode)
Сам сгенерированный код представляет из себя класс ("_SK" - skeleton), который необходимо использовать как базовый класс для своего процесса. Переопределяя виртуальные функции, и реализуя в них необходимую функциональность.
Исходный xml-файл описания необходимый для генерирования выглядит следующим образом.
<?xml version="1.0" encoding="utf-8"?>
<!--
name - название класса
msgcount - сколько сообщений обрабатывается за один раз
sleep_msec - пауза между итерациями в работе процесса
type
====
in - входные регистры (только для чтения)
out - выходные регистры (запись)
io - запись и чтение
-->
<Test>
<settings>
<set name="class-name" val="TestGen"/>
<set name="msg-count" val="20"/>
<set name="sleep-msec" val="150"/>
</settings>
<variables arg_prefix="test-">
<!-- 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"/>
</smap>
<msgmap>
<item name="msg1" comment="comment for Message 1" />
</msgmap>
</Test>
В секции <settings> описываются следующие параметры:
- class-name - Имя генерируемого класса. В итоге будет сгенерирован класс с названием "ClassName_SK" ("_SK" - сокращение от "skeleton").
- msg-count - Количество обрабатываемых за один раз(шаг) сообщений.
- sleep-msec - пауза между шагами основного цикла процесса
- base-class - Имя базового класса. По умолчанию: UniSetObject
В секции <smap> описываются "входы" и "выходы" процесса связанные с датчиками. При генерировании процесса, для каждого входа или выхода генерируется ряд свойств:
- name - идентификатор датчика связанного с этим входом или выходом (совпадает с именем указанной переменной)
- node_name - идентификатор узла датчика связанного с этим входом или выходом (по умолчанию локальный узел)
- [in |out |io]_name - переменная хранящая текущее состояние датчика (генерируется с префиксом в зависимости от vartype)
- prev_[in |out |io]_name - переменная хранящая состояние датчика на предыдущем шаге (генерируется с префиксом в зависимости от vartype)
- no_check_id - no_check_id="1" означает игнорировать (не генерировать исключение) при запуске процесса, если идентификатор датчика не найден.
Помимо этого необходимо указывать свойство iotype.
- Предупреждения:
- Поле iotype должно ОБЯЗАТЕЛЬНО совпадать с типом датчика к которому будет привязана данная переменная. Т.к. генерируется код для работы с датчиком в зависимости от его типа.
В секции <msgmap> описываются поля связанные с идентификаторами сообщений. По сути, сообщения это тоже датчики, только используемые специальным образом. Для посылки сообщения датчик выставляется в "1" и через некоторое время должен быть сброшен. (чтобы можно было опять послать тоже самое сообщение). Т.е. само событие "сообщение" это переход датчика "0" --> "1". В сгенерированном коде реализован "автоматический" сброс сообщения через
resetMsgTime миллисекунд.
resetMsgTime настраивается через конфигурационную секцию (см.
Конфигурирование ). Следует иметь ввиду, что это время должно быть достаточным чтобы датчик (изменение "0"-->"1") успел быть переданным по сети на другие узлы (зависит от используемого протокола передачи). Для сообщений генерируется такой же набор "переменных" как и для полей указанных в <smap> (см.
Секция <smap>). За исключением того, что генерируется имя с префиксом
mid_. И "привязка" идентификаторов не является обязательной.
- Предупреждения:
- Датчики-сообщений ОБЯЗАТЕЛЬНО должны иметь тип "DI"
Для работы с сообщениями существует ряд правил:
- сообщения должны посылаться при помощи специальной (сгенерированной) функции setMsg( UniSetTypes::ObjectId code, bool state ) или alarm( UniSetTypes::ObjectId code, bool state ). Для передачи сообщения необходим вызов c параметром state=true.
- Сообщения "автоматически" сбрасываются в "0" через resetMsgTime (настраиваемое в конф. секции), поэтому вызывать функции с state=false нет смысла.
В данной секции можно перечислить
переменные разных типов, для которых будет сгенерирован код по их "инициализации" и проверке "диапазона"(если указаны поля min или max). На данный момент поддерживаются переменные следующих типов:
- int - int
- float - float
- bool - bool
- str - string
Так же доступны следующие необязательные вспомогательные поля:
- min - минимальное разрешенное значение
- max - максимальное разрешенное значение
- default - значение по умолчанию (при инициализации)
- no_range_exception - не генерировать исключение в случае выхода переменной за указанный диапазон (min или max).
- const - const="1" - сгенерировать как константу.
- private | public | protected - Область видимости. По умолчанию: protected.
Помимо этого в самой секции <variables> можно указать свойство arg_prefix="...", которое используется при инициализации при помощи аргументов командной строки.
В генерируемом коде для каждой переменной происходит её инициализация по следующему шаблону (псевдокод):
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" и тогда они будут иметь соответствующую область видимости.
Для режима генерирования на основе отдельного xml-файла (
Файл описания для генерирования базового класса) необходимо дополнительно производить конфигурирование.
Конфигурирование - это привязка конкретных имён датчиков к указанным полям класса. Для этого в конфигурационном файле проекта (обычно в секции <settings>) создаётся настроечная секция для вашего объекта. А в самом классе генерируется специальный конcтруктор, позволяющий указать настроечный xml-узел:
Ниже приведён пример настроечной секции, для объекта сгенерированного на основе 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"
/>
...
Обычно для каждого объекта класса создаётся своя настроечная секция.
Дополнительно в сгенерированном коде присутствуют следующие настройки:
- sleep_msec = conf->getArgPInt("--sleep-msec","150", 150); - пауза между шагами основного цикла процесса
- resetMsgTime = conf->getPIntProp(cnode,"resetMsgTime", 2000); - время до автоматического сборса датчиков-сообщений в "0".
- smReadyTimeout = conf->getArgInt("--sm-ready-timeout",""); - время ожидания готовности SharedMemory к работе
- activateTimeout = conf->getArgPInt("--activate-timeout", 20000); - время отведённое на инициализацию процесса
- msec = conf->getArgPInt("--startup-timeout", 10000); - пауза, в течение которой игнорируется сообщение SystemMessage::WatchDog. В случае если они приходят подряд.
В генератор кода заложен специальный код для перевода процесса в тестовый режим. В этом режиме отключается вся работа процесса. Перестают обрабатываться сообщения, обновлятся входы и выходы и т.д.
Для перевода процесса в "тестовый режим" необходимо задать идентификаторы для двух "DI" датчиков:
- TestMode_S - глобальный датчик перехода в тестовый режим. Обязан быть в конфигурационном файле.
- LocalTestMode_S - датчик перевода в тестовый режим для данного процесса. Задаётся в настроечной секции данного процесса.
Переход в тестовый режим осуществляется, только если ОБА датчика станут равными "1".
- Заметки:
- Два датчика сделаны для защиты от "случайного" перехода.
uniset-codegen поддерживает генерирование двух видов процессов:
- пассивный процесс - (по умолчанию). Основан на "заказе датчиков".
- активный процесс - на каждом шаге опрашивает свои "входы".
По умолчанию используется шаблон для "пассивнного процесса". Т.е. все "in" датчики будут "заказаны" при старте процесса и далее работа будет вестись по сообщениям об изменении (UniSetTypes::SensorMessage).
Для генерирования активного процесса необходимо использовать параметр --no-ask. В таком процессе происходит активная работа с датчиками. Т.е. на каждом шаге основного цикла, происходит "принудительное" обновление значений всех "входов" (getValue) независимо от того, менялись ли они.
- Предупреждения:
- Следует иметь ввиду, что при работе не на основе "заказа датчиков", существует вероятность пропустить(потерять) "изменение" состояния датчика, в случае если он поменяется и восстановится обратно в течение времени меньше чем sleep-time у данного процесса. А также такой процесс потребляет больше процессорного времени, т.к. постоянно опрашивает "входы" независимо от того, меняли ли они своё состояние.
Шаблон
"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 зависимость на конфигурационный файл, то каждый раз при его изменении (независимо от того, что менялось), код будет перегенерироваться.
* Шаблон 'alone' не расчитан на создание "многих" объектов сгенерированного класса, т.к. они будут работать, с одним и тем же набором датчиков. Сложно представить себе пример, где бы это могло потребоваться. Более того, следует иметь ввиду, что создание нескольких объектов класса приведёт к конфликту по выставлению "out"-переменных.
Типичное правило для генерирования в Makefile.am выглядит следующим образом:
..._SOURCES= MyClass_SK.cc ...
MyClass_SK.cc: myclass.src.xml
@UNISET_CODEGEN@ -n MyClass --no-main myclass.src.xml
В этом примере
- myclass.src.xml - это файл с описанием переменных
- --no-main - отключает генерирование "запускающего" файла (функция main)
- -n - опредеяет название файлов для сгенерированного класса. В данном случае будут сгенерированы MyClass_SK.h и MyClass_SK.cc