пятница, 4 ноября 2011 г.

Khameleon tutorial

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

Вся функциональность программы заложена в модулях. Другими словами, какие модули мы загрузим, то мы и получим - от простого терминала до программы, которая связывает вместе работу нескольких устройств используя данные с одного для управления другим (например).  Список загружаемых модулей и связи между ними записаны в конфигах. Для каждой конфигурации есть два рабочих конфига с окончаниями .cfg и cfg.user. В первом записана сама конфигурация: виджеты и действия, выносимые на главное окно, опции модулей, определяющие их режим работы (т.н. скрытые опции). Во втором содержатся опции, меняющиеся в процессе работы программы (выбранные директории, отмеченные галочки, параметры окна и пр.). Эти опции автоматически сохраняются при выходе из программы, и загружаются при старте.


Подсунуть конфиг можно разными способами:
1. Через параметр -c (./idpws -c <имя_файла>.cfg). 
2. Сделать симлинк <имя_файла>; на idpws и запускать с симлинка.
3. Запускать idpws без параметров, при этом в качестве .cfg.user будет использоваться khameleon.cfg, а в качестве системного - имя, указанное параметром cfgfile (не рекомендуется).


Теперь, собственно, про конфиги самого tutotial-а. Рассмотрим их в порядке возрастания сложности:


1.  echoterm.cfg
В файле записана конфигурация для соединения друг с другом двух модулей через конвеерный (stream) интерфейс. Рассмотрим значение каждой строчки.  


Раздел [connections] содержит описание соединений между модулями. Формат записи:
<номер_cоединения>="id_<модуль_от>;signal_<имя_сигнала>;id_<модуль_куда>;slot_<имя_слота>" 
Здесь мы фактически средствами Qt соединяем сигнал одного модуля со слотом другого. Предусмотрено по одному сигналу и одному слоту на модель типа stream: через "ImReady(QVariantList)" модуль передаёт обработанные данные дальше по цепочке, а через "StartProcessing(QVariantList)" принимает данные от другого модуля. Есть ещё дополнительный сигнал "myStatus(QString)", через который может передать какие-либо сообщения в главное окно программы, где для этого предусмотрен объект logger со слотом  append(QString), или timedAppend(QString), если требуется отобразить время прихода сообщения.
 

Далее раздел [widgets]. Здесь мы пишем какие виджеты из каких модулей следует выносить на главное окно. Формат следующий:
id_<модуль>\<параметр>=<значение>
 Параметры бывают следующими: wmain, woptions, где значение, это имя заголовка окна или заголовка блока, содержащего данный виджет; wmainobjname  должно быть уникальным для каждого модуля и используется при сохранении параметров окна; woptionsdialog (true или false) - следует ли отображать виджет опций в меню опций программы; ismainlayout (true) - применяется только к одному виджету wmain, и говорит о том, что он является главным, остальные - перемещаемые.



Идём далее, раздел [modules]. Именно в этом разделе мы указываем, какие именно модули необходимо загрузить, а также присваиваем им id. Параметр id должен начинаться с нуля, быть уникальным для каждого модуля, и возрастать без пропусков (т.е. после 3 не может следовать 5). Формат записи как и в предыдущей секции:
id_<модуль>\<параметр>=<значение>
 Отличие в том, что се параметры в данной секции начинаются с точки, что говорит о том, что они являются скрытыми параметрами модуля.  ".name" - имя загружаемого модуля, ".thread" - номер потока, в который необходимо поместить данный модуль. Кроме этих параметров каждый модуль может содержать любое число индивидуальных скрытых параметров. От обычных параметров их отличает то, что они задаются в файле конфигурации и не могут изменятся во время работы программы.  Они предназначены для того, чтобы задавать конкретный вариант поведения модуля в конкретной конфигурации.


Уф, с этим файлом разобрались. Дальше будет проще и интереснее одновременно.


2.  echotermfb.cfg
Пример соединения модуля, использующего потоковый (конвейерный) интерфейс, и модуля, работающего через пропагаторы. Здесь в пояснении нуждается секция [connections], а именно, 2-ое и 3-е соединения. Остальные секции повторяют прошлую конфигурацию.  
02="id_0;out;id_1;slot_StartProcessing(QVariantList)"
03="id_1;signal_ImReady(QVariantList);id_0;in"
Во втором соединении вместо указания сигнала для первого модуля используется имя пропагатора, out. Важно, что в конструкторе модуля в лист "hpg" должен быть довавлен элемент, с именем "out" и значением NULL (создание объекта пропагатора происходит в ядре во время соединения модулей). Также важно заметить, что для связки таких разнородных модулей можно использовать только пропагаторы без обратной связи, сигнал пропагатора "trySendList(QVariantList)", чтобы ловить пришедшие данные, и функцию "sendList(QVariantList)", чтобы отправить данные.


3.  echofbterm.cfg
Это также пример соединения разнородных модулей. Разность в конфиге с предыдущим примером минимальна, но заинтересовавшиеся могут найти немного нового в исходных кодах модулей.


4.  echofbtermfb.cfg
И наконец, пример взаимодействия двух модулей через пропагаторы. Здесь в секции [connections] последние две строчки заменены одной, но в конце появилась подпись "feedback":
02="id_0;interface;id_1;io;feedback"
Что здесь написано: пропагатор модуля id_0 соединить с пропагатором модуля с id_1, при этом первый пропагатор должен дождаться ответа от второго, и только после этого вернуть управления модулю с id_0. В конструкторе модуля такой пропагатор мы уже должны поместить в лист "hpgfb", и вместо функций "send(QByteArray)" и "sendList(QVariantList)" следует использовать read, write, readList и writeList, в зависимости от передаваемых данных. Единственное требование, оба модуля должны быть согласованы по типу данных. Т.е., фактически мы получаем возможность читать и записывать данные в модуль, и неважно, что он работает в другом потоке и живёт своей жизнью. Если заглянуть в код "ведомого" модуля (из которого читают), то там можно найти вызов функции readReturn writeReturn, readListReturn или writeListReturn. Если это обработка ведомого пропагатора, то необходимо вызывать одну из этих функций перед возвратом из обработчика. Какую именно - зависит от выполняемой операции и от типа данных. Данные, положенные в эти функции "вернуться" модулю, вызывающему соответствующие ответные функции из своего пропагатора.


На этом я заканчиваю краткое введение в принцип работы Модульной Системы. Если для кого-либо эта информация будет полезной, и он захочет присоединится к разработке, использовать их для своих нужд, буду рад ответить на вопросы.