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

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

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

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

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

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

  • Обновление входов (в случае работы на основе заказа датчиков, этот шаг отсутствует)
  • проверка срабатывания внутренних таймеров
  • Обработка сообщений в очереди сообщений
  • Выполнение шага алгоритма (функция step)
  • обновление датчика "сердцебиения" /см. Слежение за "живостью" объектов ("сердцебиение") /
  • Обновление выходов
Предупреждения
По умолчанию, генерируется скелет для "пассивного процесса" (см. Типы процессов (активный и пассивный)), в котором "ВСЕ "выходы"(out_xxx) перезаписывают свои значения в SharedMemory(SM) только по изменению. Поэтому если в вашем процессе вы не меняли переменную out_xxx, а какой-то другой процесс обновил значение в SM, то значение в SM так и останется и не будет перезаписано Вашим процессом, пока он не обновит у себя значение out_xxx. Т.е. ПО УМОЛЧАНИЮ СЧИТАЕТСЯ (в целях оптимизации и уменьшения количества "удалённых вызовов"), что каждый процесс является ЕДИНОЛИЧНЫМ ВЛАДЕЛЬЦЕМ своих ВЫХОДОВ и никто кроме него не может их "обновлять" в SM. Для включения перезаписывания out_xxx на каждом шаге, можно воспользоваться ключом \b forceOut="1" в настроечной секции процесса. Или указать \b force="1" для конкретного выхода в src.xml (см. \ref pg_Codegen_SMap ). Помимо этого обрабатывается специальный режим: \ref pg_Codegen_TestMode Сам сгенерированный код представляет из себя класс ("_SK" - skeleton), который необходимо использовать как базовый класс для своего процесса. Переопределяя виртуальные функции, и реализуя в них необходимую функциональность. @section pg_Codegen_XmlFile Файл описания для генерирования базового класса Исходный xml-файл описания необходимый для генерирования выглядит следующим образом. @code <?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"/> <set name="logrotate" val="append"/> </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> \endcode @section pg_Codegen_Settings Секция <settings> В секции \<settings> описываются следующие параметры: - \b class-name - Имя генерируемого класса. В итоге будет сгенерирован класс с названием \b "ClassName_SK" (\a "_SK" - сокращение от "skeleton"). - \b msg-count - Количество обрабатываемых за один раз(шаг) сообщений. - \b sleep-msec - пауза между шагами основного цикла процесса - \b base-class - Имя базового класса. По умолчанию: UniSetObject @section pg_Codegen_SMap Секция <smap> В секции \<smap> описываются "входы" и "выходы" процесса связанные с датчиками. При генерировании процесса, для каждого входа или выхода генерируется ряд свойств: - \b name - идентификатор датчика связанного с этим входом или выходом (совпадает с именем указанной переменной) - \b node_name - идентификатор узла датчика связанного с этим входом или выходом (по умолчанию локальный узел) - [\b in |\b out |\b io]\b _name - переменная хранящая текущее состояние датчика (генерируется с префиксом в зависимости от \b vartype) - \b prev_[\b in |\b out |\b io]\b _name - переменная хранящая состояние датчика на предыдущем шаге (генерируется с префиксом в зависимости от \b vartype) - \b no_check_id - no_check_id="1" означает игнорировать (не генерировать исключение) при запуске процесса, если идентификатор датчика не найден. - \b force="1" - принудительно перезаписывать значение в SharedMemory на каждом шаге. Действует только для \b vartype="out" - \b loglevel="1" - признак того, что данный датчик управляет логами для этого процесса. См. \ref pg_Codegen_loglevel. - \b initFromSM="1" - инициализировать значение из SM при старте (точнее при получении StartUp). Действует и на "in" и на "out". \note Правильнее указывать и \i необязательное поле \b iotype, которое должно совпадать с типом датчика к которому будет привязана данная переменная. Это позволит утилите \b uniset-linkeditor отслеживать правильность типов датчиков при "привязке". @section pg_Codegen_MsgMap Секция <msgmap> В секции \<msgmap> описываются поля связанные с идентификаторами сообщений. По сути, сообщения это тоже датчики, только используемые специальным образом. Для посылки сообщения датчик выставляется в "1" и через некоторое время должен быть сброшен. (чтобы можно было опять послать тоже самое сообщение). Т.е. само событие "сообщение" это переход датчика "0" --> "1". \warning В сгенерированном коде реализован "автоматический" сброс сообщения через \b resetMsgTime миллисекунд. \b resetMsgTime настраивается через конфигурационную секцию (см. \ref pg_Codegen_Configuration ). Следует иметь ввиду, что это время должно быть достаточным чтобы датчик (изменение "0"-->"1") успел быть переданным по сети на другие узлы (зависит от используемого протокола передачи). Либо можно переопределить значение аргументом командной строки \a --argprefix-resetMsgTime \a msec, либо задать в файле описания в секции \b <settings> @code <settings> ... <set name="resetMsgTime" val="500"/> </settings> \endcode Для сообщений генерируется такой же набор "переменных" как и для полей указанных в \<smap> (см. \ref pg_Codegen_SMap). За исключением того, что генерируется имя с префиксом \b mid_. И "привязка" идентификаторов не является обязательной. Для работы с сообщениями существует ряд правил: - сообщения должны посылаться при помощи специальной (сгенерированной) функции \b setMsg( UniSetTypes::ObjectId code, bool state ). Для передачи сообщения необходим вызов c параметром \b state=true. - Сообщения "автоматически" сбрасываются в "0" через \b resetMsgTime (настраиваемое в конф. секции), поэтому вызывать функции с \b state=false нет смысла. Если указать resetMsgTime <=0 - автоматический сброс происходить не будет, и разработчик должен самостоятельно заботиться об этом. @section pg_Codegen_Variables Секция <variables> В данной секции можно перечислить \b переменные разных типов, для которых будет сгенерирован код по их "инициализации" и проверке "диапазона"(если указаны поля min или max). На данный момент поддерживаются переменные следующих типов: - \b int - int - \b long - long - \b float - float - \b double - double - \b bool - bool - \b str - std::string - \b sensor - sensor // ObjectId инициализируемый как conf->getSensorID(name) - \b object - object // ObjectId инициализируемый как conf->getObjectID(name) Так же доступны следующие необязательные вспомогательные поля: - \b min - минимальное разрешенное значение - \b max - максимальное разрешенное значение - \b default - значение по умолчанию (при инициализации) - \b no_range_exception - не генерировать исключение в случае выхода переменной за указанный диапазон (\b min или \b max). - \b const - const="1" - сгенерировать как константу. - \b private | \b public | \b protected - Область видимости. По умолчанию: \b protected. - \b no_vmonit="1" - отключение "удалённого контроля за переменной". См. \ref pg_Codegen_vmonitor. В генерируемом коде для каждой переменной происходит её инициализация по следующему шаблону (псевдокод): @code varname = conf->getArgParam("--'arg_prefix'varname'",it.getProp("'varname'")); if( varname.empty() ) varname = 'default' \endcode Где \a it.getProp() - получение значения из соответствующей настроечной секции в конфигурационном файле (см. \ref pg_Codegen_Configuration). Из кода можно видеть, что приоритетным является аргумент командной строки, потом значение из конф. файла и только потом \a default. Если указаны поля \b min или \b max происходит проверка значения (после инициализации) на соответствие указанному диапазону. По умолчанию, при выходе за диапазон, генерируется исключение. Но если указано \b no_range_exception="1", то просто выдаётся warning в unideb[Debug::WARN]. По умолчанию эти поля генерируются как \b protected. Но если есть необходимость, то можно указать свойство \b public="1" или \b private="1" и тогда они будут иметь соответствующую область видимости. @section pg_Codegen_argprefix Аргументы командной строки Для возможности переопределять различные параметры при помощи аргументов командной строки, сделан следующий механизм. В сгенерированном конструкторе (файл xx_SK.h) можно задать последний аргумент \b argprefix. Если он равен "", то в качестве префикса используется ObjectName. Все переменные из секции \a <variables> можно переопределять аргументом \b --prefix-varname \b val. В общем случае это будет: \b --ObjectName-varname \b val. Помимо этого \b argprefix можно задать в секции <settings> @code <settings> ... <set name="arg-prefix" val="test-"/> ... </settings> \endcode Тогда вместо \a ObjectName будет использоваться указанный префикс \a --test- (обратите внимание на то, что префикс задаётся с \b - в конце). Значения по умолчанию для \b входов (in_xxx) и \b выходов (out_xx) можно также определять через аргументы командной строки: - \b --argprefix-xxxname-default val Дополнительно при помощи командной строки можно переопределять следующие свойства: - \b --argprefix-force-out 1/0 - \b --argprefix-heartbeat-id sensorID - \b --argprefix-heartbeat-time msec - \b --argprefix-heartbeat-max num - \b --argprefix-sleep-msec msec - \b --argprefix-resetMsgTime msec - \b --argprefix-sm-test-id sensorID - \b --argprefix-activate-timeout msec - \b --argprefix-startup-timeout msec - \b --argprefix-log-[add|del]-levels xxx,xxx,xxx @section pg_Codegen_Configuration Конфигурирование Для режима генерирования на основе отдельного xml-файла (\ref pg_Codegen_XmlFile) необходимо дополнительно производить конфигурирование. \a Конфигурирование - это привязка конкретных имён датчиков к указанным полям класса. Для этого в конфигурационном файле проекта (обычно в секции \<settings>) создаётся настроечная секция для вашего объекта. А в самом классе генерируется специальный конструктор, позволяющий указать настроечный xml-узел: @code ClassName( UniSetTypes::ObjectId id, xmlNode* node=UniSetTypes::conf->getNode("ClassName") ); \endcode Ниже приведён пример настроечной секции, для объекта сгенерированного на основе xml-файла указанного в \ref pg_Codegen_XmlFile @code ... <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" /> ... \endcode Обычно для каждого объекта класса создаётся своя настроечная секция. Дополнительно в сгенерированном коде присутствуют следующие настройки: - \b sleep_msec = conf->getArgPInt("--sleep-msec","150", 150); - пауза между шагами основного цикла процесса - \b resetMsgTime = conf->getPIntProp(cnode,"resetMsgTime", 2000); - время до автоматического сброса датчиков-сообщений в "0". - \b smReadyTimeout = conf->getArgInt("--sm-ready-timeout",""); - время ожидания готовности SharedMemory к работе - \b activateTimeout = conf->getArgPInt("--activate-timeout", 20000); - время отведённое на инициализацию процесса - \b msec = conf->getArgPInt("--startup-timeout", 10000); - пауза, в течение которой игнорируется сообщение SystemMessage::WatchDog. В случае если они приходят подряд. @section pg_Codegen_TestMode Специальный режим "тест" (TestMode) В генератор кода заложен специальный код для перевода процесса в тестовый режим. В этом режиме отключается вся работа процесса. Перестают обрабатываться сообщения, обновляться входы и выходы и т.д. Для перевода процесса в "тестовый режим" необходимо задать идентификаторы для двух "DI" датчиков: - \b TestMode_S - глобальный датчик перехода в тестовый режим. \b Обязан быть в конфигурационном файле. - \b LocalTestMode_S - датчик перевода в тестовый режим для данного процесса. Задаётся в настроечной секции данного процесса. Переход в тестовый режим осуществляется, только если \b ОБА датчика станут равными \b "1". \note Два датчика сделаны для защиты от "случайного" перехода. @section pg_Codegen_Templ_Ask Типы процессов (активный и пассивный) uniset-codegen поддерживает генерирование двух видов процессов: - \b пассивный \b процесс - (по умолчанию). Основан на "заказе датчиков". - \b активный \b процесс - на каждом шаге опрашивает свои "входы". По умолчанию используется шаблон для "пассивного процесса". Т.е. все \b "in" датчики будут "заказаны" при старте процесса и далее работа будет вестись по сообщениям об изменении (UniSetTypes::SensorMessage). Для генерирования \b активного \b процесса необходимо использовать параметр \b --no-ask. В таком процессе происходит \a активная работа с датчиками. Т.е. на каждом шаге основного цикла, происходит "принудительное" обновление значений всех "входов" (getValue) независимо от того, менялись ли они. \warning Следует иметь ввиду, что при работе не на основе "заказа датчиков", существует вероятность пропустить(потерять) "изменение" состояния датчика, в случае если он поменяется и восстановится обратно в течение времени меньше чем \b sleep-time у данного процесса. А также такой процесс потребляет больше процессорного времени, т.к. постоянно опрашивает "входы" независимо от того, меняли ли они своё состояние. @section pg_Codegen_Logrotate Настройка ротации логов В разделе '<settings>' можно задавать параметр \b logrotate=[truncate|append]. Он определяет как будет обработана команда SystemMessage::Logrotate, по которой происходит переоткрытие логфайла (если включено ведение логфайла). - \b truncate - при переоткрытии "обрезать" файл - \b append - при переоткрытии продолжить писать логи в конец файла Режим \b append используется по умолчанию. Он удобен для использования совместно с внешней системой ротации логов, например logrotate. Которая после проведения ротации может обрезать файл и посылать ВСЕМ процессам команду LogRotate по которой файл логов будет переоткрыт, если он был заархивирован. При этом те процессы чей файл не был заархивирован, продолжат писать дальше в конец логфайла. @section pg_Codegen_Templ_Alone Шаблон 'Alone' Шаблон \b "Alone" предназначен для генерирования \b без \b использования специального xml-файла с описанием переменных. Генерирование происходит непосредственно по конфигурационному файлу проекта. Для этого всё-равно необходимо создать соответствующую настроечную секцию, в которой будут прописаны параметры необходимые для генерирования "SK"-класса. При этом используемые "входы" и "выходы" записываются непосредственно у каждого используемого датчика в секции \b \<consumers>. Ниже приведён пример конфигурирования процесса файле проекта: @code ... <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> \endcode Как видно из примера, \b vartype переменных записывается непосредственно в свойствах \b \<consumer>. \warning * Следует иметь ввиду, что при изменении конфигурационного файла, необходимо перегенерировать код. И в свою очередь, если поставить в Makefile зависимость на конфигурационный файл, то каждый раз при его изменении (независимо от того, что менялось), код будет перегенерироваться. \warning * Шаблон 'alone' не рассчитан на создание "многих" объектов сгенерированного класса, т.к. они будут работать, с одним и тем же набором датчиков. Сложно представить себе пример, где бы это могло потребоваться. Более того, следует иметь ввиду, что создание нескольких объектов класса приведёт к конфликту по выставлению "out"-переменных. @section pg_Codegen_Make Параметры генерирования кода Типичное правило для генерирования в Makefile.am выглядит следующим образом: @code ..._SOURCES= MyClass_SK.cc ... MyClass_SK.cc: myclass.src.xml @UNISET_CODEGEN@ -n MyClass --no-main myclass.src.xml \endcode В этом примере - \b myclass.src.xml - это файл с описанием переменных - \b --no-main - отключает генерирование "запускающего" файла (функция main) - \b -n - определяет название файлов для сгенерированного класса. В данном случае будут сгенерированы \a MyClass_SK.h и \a MyClass_SK.cc @section pg_Codegen_log Логи Для логирования событий каждый класс содержит объект mylog (класса DebugStream) и несколько предопределённых макросов для удобства использования. - myinfo - info - mywarn - warn - mycrit - crit - mylog1..mylog9 - вывод в уровни level1....level9 - mylogany - any Так же можно использовать функции напрямую: @code mylog->info() << "....test..." << endl; mylog->log1() << "....test..." << endl; \endcode \p Помимо объекта mylog класс содержит ряд вспомогательных функций: - \b string \b dumpIO() - вывод состояния всех входов и выходов в строку, в виде: @code ObjectName: in_input1_s(Sensor1_S)=1 in_output2_c(Output1_C)=0 ... \endcode - \b string \b str(ObjectId,showLinkName) - вывод названия указанного входа или выхода в формате "in_input1_s(Sensor1_S)". Если showLinkName=false, то будет сформирована строка "in_input1_s". Пример использования: @code .. myinfo << str(input1_s) << endl; \endcode - \b string \b strval(ObjectId,showLinkName) - вывод названия и текущего значения указанного входа или выхода в формате "in_input1_s(Sensor1_S)=1". Если showLinkName=false, то будет сформирована строка "in_input1_s=1". Пример использования: @code .. myinfo << strval(input1_s) << endl; \endcode @section pg_Codegen_loglevel Управление логами объекта (процесса) через специальный датчик Для динамического управления логами процесса предусмотрен механизм, который позволяет указать аналоговый датчик (AI), значение которого будет использовано как указание уровня вывода логов (см. Debug::type). Чтобы назначить специальный "вход" (vartype="in") для управления логами, достаточно в секции \b <smap> \a ТОЛЬКО \a ДЛЯ \a ОДНОГО входа указать loglevel="1". Пример: @code <smap> ... <item name="loglevel_s" vartype="in" comment="log level control" loglevel="1"/> </smap> \endcode Данный механизм позволяет посредством этого датчика управлять уровнем вывода логов (mylog) во время работы процесса. Для удобства предусмотрена утилита \b uniset2-log2val позволяющая преобразовать текстовые названия уровней в число, которое необходимо выставить датчику. Пример: @code uniset2-log2val info,level1,warn,level9 4117 \endcode Т.е. для того, чтобы включить указанные логи в датчик нужно записать число \b 4117 @section pg_Codegen_logserver Удалённое управление логами Для возможности удалённого управления логами (включением, отключением, просмотром, и т.п.) в каждом объекте предусмотрена возможность запуска LogServer, который \b запускается на порту \b равном \b ID-объекта. По умолчанию LogServer не запускается (выключен). Для его запуска необходимо указать: @code --argprefix-run-logserver \endcode Дополнительно доступны следующие аргументы: @code --argprefix-logserver-host - по умолчанию localhost --argprefix-logserver-port - по умолчанию ID-объекта \endcode Непосредственно управление логами производиться при помощи утилиты \b uniset2-log @code -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. \endcode @section pg_Codegen_vmonitor Мониторинг состояния внутренних переменных В генерируемом коде встроена поддержка удалённого просмотра (мониторинга) внутреннего состояния \b ВСЕХ in_xx и out_xxx переменных, а также переменных определённых пользователем. Для этого достаточно использовать утилиту uniset2-vmonit @code Usage: uniset2-vmonit [-s watch_sec] OBJECT_ID[@node] [uniset-admin-options] \endcode Где можно указать (-s sec) как часто обновлять информацию и собственно указать ID (или name) объекта (процесса). Утилита требует наличия в системе утилиты watch и на самом деле является обёрткой над утилитой uniset2-admin. Например для отслеживания процесса 12000 утилита запускается так: @code uniset2-vmonit 12000 \endcode \note При локальной наладке обычно запуск производиться так: uniset2-start.sh -f uniset2-vmonit ObjectID @subsection pg_Codegen_vmonitor_myvar Добавление своих переменных в "мониторинг". Для добавления своей переменной в "мониторинг" достаточно один раз (например в конструкторе своего класса) вызвать функцию (на самом деле макрос) 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
На данный момент поддерживаются только простые типы переменных (bool,short,int,long,double,float см. VMonitor)

Для пользовательской информации введена виртуальная функция std::string getMonitInfo(), переопределив которую, можно сформировать свою информацию, которую можно будет удалённо читать.

HTTP API

Все обращения к api должны быть по такому пути `/api/VERSION/command?params..`

/help - Получение списка доступных команд
/ - Получение стандартной информации
/params/get?param1&param2 - Получить текущее значение указанных параметров. Если параметры не указаны, будут возвращены все доступные.
/params/set?param1=val1&param2=val2&.. - Установить значения для указанных параметров.