UniSet  1.4.0
Генератор кода uniset-codegen

Общее описание утилиты uniset-codegen

Утилита uniset-codegen предназначена для генерирования "скелета" процесса управления на основе заданного xml-описания. В скелет процесса включается "рутинный" код по получению и проверке индетификаторов, переменных, обработке сообщений и ряд других вспомогательных функций. Помимо этого определяются базовые виртуальные функции, участвующие в процессе.

Процесс можно сгенерировать по нескольким шаблонам

Заметки:
Генерирование на основе отдельного xml-файла удобно если вам необходимо создавать несколько объектов вашего класса, отличающихся только набором датчиков.
Генерирование на основе кофигурационного файла проекта (шаблон 'Alone') удобно использовать когда у вас планируется один единственный объект вашего класса. Т.к. в данном случае привязка производиться непосредственно к конкретным датчикам в конфигурационном файле.

Генерируемый процесс функционирует по следующему обобщённому алгоритму:

Помимо этого обрабатывается специальный режим: Специальный режим "тест" (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>

В секции <settings> описываются следующие параметры:

Секция <smap>

В секции <smap> описываются "входы" и "выходы" процесса связанные с датчиками. При генерировании процесса, для каждого входа или выхода генерируется ряд свойств:

Помимо этого необходимо указывать свойство iotype.

Предупреждения:
Поле iotype должно ОБЯЗАТЕЛЬНО совпадать с типом датчика к которому будет привязана данная переменная. Т.к. генерируется код для работы с датчиком в зависимости от его типа.

Секция <msgmap>

В секции <msgmap> описываются поля связанные с идентификаторами сообщений. По сути, сообщения это тоже датчики, только используемые специальным образом. Для посылки сообщения датчик выставляется в "1" и через некоторое время должен быть сброшен. (чтобы можно было опять послать тоже самое сообщение). Т.е. само событие "сообщение" это переход датчика "0" --> "1". В сгенерированном коде реализован "автоматический" сброс сообщения через resetMsgTime миллисекунд. resetMsgTime настраивается через конфигурационную секцию (см. Конфигурирование ). Следует иметь ввиду, что это время должно быть достаточным чтобы датчик (изменение "0"-->"1") успел быть переданным по сети на другие узлы (зависит от используемого протокола передачи). Для сообщений генерируется такой же набор "переменных" как и для полей указанных в <smap> (см. Секция <smap>). За исключением того, что генерируется имя с префиксом mid_. И "привязка" идентификаторов не является обязательной.

Предупреждения:
Датчики-сообщений ОБЯЗАТЕЛЬНО должны иметь тип "DI"

Для работы с сообщениями существует ряд правил:

Секция <variables>

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

Так же доступны следующие необязательные вспомогательные поля:

Помимо этого в самой секции <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-узел:

    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"
    />
    ...

Обычно для каждого объекта класса создаётся своя настроечная секция.

Дополнительно в сгенерированном коде присутствуют следующие настройки:

Специальный режим "тест" (TestMode)

В генератор кода заложен специальный код для перевода процесса в тестовый режим. В этом режиме отключается вся работа процесса. Перестают обрабатываться сообщения, обновлятся входы и выходы и т.д.

Для перевода процесса в "тестовый режим" необходимо задать идентификаторы для двух "DI" датчиков:

Переход в тестовый режим осуществляется, только если ОБА датчика станут равными "1".

Заметки:
Два датчика сделаны для защиты от "случайного" перехода.

Типы процессов (активный и пассивный)

uniset-codegen поддерживает генерирование двух видов процессов:

По умолчанию используется шаблон для "пассивнного процесса". Т.е. все "in" датчики будут "заказаны" при старте процесса и далее работа будет вестись по сообщениям об изменении (UniSetTypes::SensorMessage).

Для генерирования активного процесса необходимо использовать параметр --no-ask. В таком процессе происходит активная работа с датчиками. Т.е. на каждом шаге основного цикла, происходит "принудительное" обновление значений всех "входов" (getValue) независимо от того, менялись ли они.

Предупреждения:
Следует иметь ввиду, что при работе не на основе "заказа датчиков", существует вероятность пропустить(потерять) "изменение" состояния датчика, в случае если он поменяется и восстановится обратно в течение времени меньше чем sleep-time у данного процесса. А также такой процесс потребляет больше процессорного времени, т.к. постоянно опрашивает "входы" независимо от того, меняли ли они своё состояние.

Шаблон 'Alone'

Шаблон "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

В этом примере