Bubot в деталях — различия между версиями
м (Разговоров Михаил переименовал страницу Bubot в деталях. в Bubot в деталях без оставления перенаправления) |
(→Пример логики страницы) |
||
(не показано 17 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
− | Данная статья является логическим продолжением обзорной статьи о фреймворке и предлагает более детальный обзор возможностей предоставляемых разработчику. | + | Данная статья является логическим продолжением [[Bubot|обзорной статьи]] о фреймворке и предлагает более детальный обзор возможностей предоставляемых разработчику. |
− | Самым важным при создании своего робота это является его проектирование - | + | Самым важным при создании своего робота это является его проектирование - составление схемы будущих сервисов, и порядка их взаимодействия. Что в свою очередь не возможно без понимания возможностей фреймворка. |
− | Как было сказано ранее, фреймворк представляет собой набор поддерживаемых пользователем модулей реализующих различные функции робототехники. При работе Bubot строит сеть из процессов, которые могут асинхронно получать и отправлять сообщения между собой. Где процесс - это запущенный с определенными параметрами экземпляр модуля. | + | Как было сказано ранее, фреймворк представляет собой набор поддерживаемых пользователем модулей реализующих различные функции робототехники. При работе Bubot строит сеть из процессов, которые могут асинхронно |
+ | получать и отправлять сообщения между собой. Где процесс - это запущенный с определенными параметрами экземпляр модуля. | ||
− | Модуль | + | Модуль это основной элементом фреймворка и свой обзор я начну именно с него. Модули фреймворка являются, в том числе и модулями питона, поэтому чтобы между ними не было путаницы в рамках фреймворка наши модули называются Buject-ами, находятся в каталоге buject и являются наследниками от одноименного класса или его потомков. |
− | Базовый класс Buject предоставляет разработчику возможности хранения параметров в общей памяти, доступ к параметрам других модулей, систему обмена сообщениям. | + | Базовый класс Buject предоставляет разработчику возможности хранения параметров в общей памяти, доступ к параметрам других модулей, систему обмена сообщениям. |
− | Любой Buject состоит из двух файлов, один файл это собственно код на языке Python 3, а другой это конфиг модуля описывающий его назначение, параметры, методы и | + | Любой Buject состоит из двух файлов, один файл это собственно код на языке Python 3, а другой это конфиг модуля описывающий его назначение, параметры, методы и список зависимостей. |
− | |||
− | |||
− | |||
+ | === Общая структура пользовательского модуля. === | ||
<source lang=python> | <source lang=python> | ||
from buject.Buject import Buject | from buject.Buject import Buject | ||
Строка 24: | Строка 23: | ||
# В качестве базового класса может выступать как сам базовый модуль Buject | # В качестве базового класса может выступать как сам базовый модуль Buject | ||
# так и любой из его потомков. | # так и любой из его потомков. | ||
− | # Например для всех моторов создан класс Motor, который задает общий | + | # Например для всех моторов создан класс Motor, который задает общий интерфейс для работы с данным типом устройств |
− | # а для | + | # а для подключения мотора скажем через контроллер PCA9685 мы создаем свой модуль унаследованный от модуля Motor |
− | # в котором реализуем | + | # в котором реализуем интерфейс применительно к конкретному железу. Своеобразный драйвер устройства. |
def __init__(self, user_config=None): | def __init__(self, user_config=None): | ||
super(BujectName, self).__init__(user_config) # читаем конфиги | super(BujectName, self).__init__(user_config) # читаем конфиги | ||
self.my_const_x = 'XXX' # определяете необходимые Вам константы. | self.my_const_x = 'XXX' # определяете необходимые Вам константы. | ||
# все настраиваемые параметры должны быть описаны в конфиге | # все настраиваемые параметры должны быть описаны в конфиге | ||
+ | |||
+ | |||
+ | def on_run(self): | ||
+ | # метод вызывается сразу после запуска процесса | ||
+ | return | ||
+ | |||
def on_ready(self): | def on_ready(self): | ||
− | # метод | + | # метод вызывается перед запуском основного цикла. |
− | # в этот момент все параметры сервиса | + | # в этот момент все параметры сервиса инициализированы |
− | # | + | # необходимые для старта процессы уже запустились |
# процесс подписан на все необходимые сообщения | # процесс подписан на все необходимые сообщения | ||
− | |||
− | |||
− | |||
− | |||
return | return | ||
Строка 47: | Строка 48: | ||
# отрицательное значение max_fps задает частоту не чаще одного раза в Х секунд | # отрицательное значение max_fps задает частоту не чаще одного раза в Х секунд | ||
# метод автоматически один раз в мекунду обновляет параметры и статус модуля в разделяемой памяти | # метод автоматически один раз в мекунду обновляет параметры и статус модуля в разделяемой памяти | ||
− | # большой fps существенно увеличивает нагрузку на процессор из- | + | # большой fps существенно увеличивает нагрузку на процессор из-за redis |
− | # если | + | # если мощности Вашего процессора не хватает требуемый fps, то имеет смысл вынести этот алгоритм в систему реального времени, |
− | # например на базе arduino, которой в свою очередь управлять из модуля | + | # например на базе arduino, которой в свою очередь управлять из модуля. |
+ | |||
+ | |||
return | return | ||
def on_terminate(self): | def on_terminate(self): | ||
− | # метод | + | # метод вызывается после остановки сервиса |
return | return | ||
Строка 69: | Строка 72: | ||
def action_XXX(self): | def action_XXX(self): | ||
− | # Вы можете | + | # Вы можете определить любые Ваши методы, хорошим тоном будет использование префикса, |
# чтобы другие разработчики могли отличать пользовательские методы от методов фреймворка. | # чтобы другие разработчики могли отличать пользовательские методы от методов фреймворка. | ||
Строка 108: | Строка 111: | ||
</source> | </source> | ||
− | Общая структура конфига пользовательского модуля | + | ===Общая структура конфига пользовательского модуля.=== |
<source lang=javascript> | <source lang=javascript> | ||
{ | { | ||
Строка 114: | Строка 117: | ||
"name": { | "name": { | ||
"value": "BujectFAQ" // имя сервиса по умолчанию, обязателен | "value": "BujectFAQ" // имя сервиса по умолчанию, обязателен | ||
− | // если в системе | + | // если в системе будет запущен только один экземпляр этого модуля (сервис), |
// то этого имени будет достаточно | // то этого имени будет достаточно | ||
}, | }, | ||
Строка 126: | Строка 129: | ||
"value": "default value", // значение по умолчанию | "value": "default value", // значение по умолчанию | ||
"description": "Пользовательский параметр 1", // описание параметра | "description": "Пользовательский параметр 1", // описание параметра | ||
− | "type": "int" // тип, используется при | + | "type": "int" // тип, используется при изменении значения параметра |
// через пользовательский интерфейс. | // через пользовательский интерфейс. | ||
// поддерживаются int (приведение строки из поля ввода к числу). | // поддерживаются int (приведение строки из поля ввода к числу). | ||
Строка 140: | Строка 143: | ||
"value": "default value", // инициализируется значением по умолчанию | "value": "default value", // инициализируется значением по умолчанию | ||
"description": "user status 1", // описание статуса | "description": "user status 1", // описание статуса | ||
− | "type": "unixtime" // тип, используется при | + | "type": "unixtime" // тип, используется при визуализации значения |
// в пользовательском интерфейсе. | // в пользовательском интерфейсе. | ||
// поддерживаются unixtime (форматирование в строку при выводе) | // поддерживаются unixtime (форматирование в строку при выводе) | ||
Строка 146: | Строка 149: | ||
} | } | ||
}, | }, | ||
− | "depend_buject": {// список | + | "depend_buject": {// список необходимых для запуска модулей |
− | "PCA9685": {} // | + | "PCA9685": {} // например данный процесс не будет запущен пока не стартует |
− | + | // процесс с именем PCA9685 (status.buject = 'ready') | |
}, | }, | ||
"incoming_request": { // список входящих запросов которые может обрабатывать модуль | "incoming_request": { // список входящих запросов которые может обрабатывать модуль | ||
"set_power": { // название запроса | "set_power": { // название запроса | ||
− | "description": "Установить мощность | + | "description": "Установить мощность мотора", // описание |
"param": { // список параметров принимаемых запросом, используется при отладке | "param": { // список параметров принимаемых запросом, используется при отладке | ||
// и конфигурировании робота через веб интерфейс | // и конфигурировании робота через веб интерфейс | ||
Строка 160: | Строка 163: | ||
"type": "int" | "type": "int" | ||
}, | }, | ||
− | "response": 1 // указывается для | + | "response": 1 // указывается для информирования о том, что метода может быть направлен ответ |
} | } | ||
} | } | ||
Строка 173: | Строка 176: | ||
} | } | ||
}, | }, | ||
− | "outgoing_event": { // список событий | + | "outgoing_event": { // список событий происходящих в модуле |
"move_detected": { // название события | "move_detected": { // название события | ||
"description": "Обнаружено движение", // описание | "description": "Обнаружено движение", // описание | ||
Строка 190: | Строка 193: | ||
"bubot": "other bubot", // указывается, если получателем запроса является другой бубот | "bubot": "other bubot", // указывается, если получателем запроса является другой бубот | ||
"buject": "room_control", // название сервиса получателя запроса, обязателен | "buject": "room_control", // название сервиса получателя запроса, обязателен | ||
− | "name": "move_detected", // название | + | "name": "move_detected", // название сообщения на стороне инициатора |
− | "description": "В комнате сработал датчик движения" | + | "description": "В комнате сработал датчик движения", |
+ | // если названия bubot и buject опущено, то имеется ввиду сообщения текущего бубота | ||
+ | "param": { // список параметров события, используется при отладке | ||
+ | // и конфигурировании робота через веб интерфейс - необязателен | ||
+ | "param_name": { // состав и назначение аналогично param, см. выше | ||
+ | "value": "default value", | ||
+ | "description": "Пользовательский параметр 1", | ||
+ | "type": "int" | ||
+ | } | ||
} | } | ||
} | } | ||
Строка 201: | Строка 212: | ||
Плавно переходим к тому как запускаются модули, а если быть точнее - процессы. Один модуль можно запустить в неограниченном количестве экземпляров, например в случаях когда у нас несколько моторов или датчиков. | Плавно переходим к тому как запускаются модули, а если быть точнее - процессы. Один модуль можно запустить в неограниченном количестве экземпляров, например в случаях когда у нас несколько моторов или датчиков. | ||
− | Список | + | Список будущих процессов их параметры и взаимосвязи описываются в конфиге робота. По структуре он практически идентичен конфигу модуля. Рассмотрим его по подробнее. |
+ | ===Структура конфига робота.=== | ||
<source lang=javascript> | <source lang=javascript> | ||
{ | { | ||
Строка 212: | Строка 224: | ||
"depend_buject": {// список сервисов и их параметры которые будут запущены | "depend_buject": {// список сервисов и их параметры которые будут запущены | ||
"PCA9685": { // название сервиса, содержимое объекта содержит описание запускаемого процесса аналогично описанию модуля, указываем только параметры значения которых отличаются от значений по умолчанию | "PCA9685": { // название сервиса, содержимое объекта содержит описание запускаемого процесса аналогично описанию модуля, указываем только параметры значения которых отличаются от значений по умолчанию | ||
+ | "param": { // следующий ниже следующий пример запустит на базе модуля BujectFAQ процесс с именем Test, в котором от значений по умолчанию будет отличаться только один user_parameter_1 | ||
+ | "name": {"value": "Test"}, | ||
+ | "buject": {"value": "BujectFAQ"}, | ||
+ | "user_parameter_1": { "value": "33"} | ||
+ | } | ||
} | } | ||
}, | }, | ||
− | "user": { // список пользователей имеющих доступ в веб-интерфейс | + | "user": { // список пользователей имеющих доступ в веб-интерфейс, если раздел не указывать то доступ не контролируется |
+ | "Businka": { // Логин, | ||
+ | "password": "" // Пароль, можно не указывать | ||
+ | } | ||
} | } | ||
} | } | ||
Строка 221: | Строка 241: | ||
Конфиги роботов должны располагаться в каталоге config. В качестве примера можно посмотреть конфиг scout. | Конфиги роботов должны располагаться в каталоге config. В качестве примера можно посмотреть конфиг scout. | ||
− | При запуске робота будут асинхронно запущены все процессы перечисленные в depend_buject. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему | + | При запуске робота будут асинхронно запущены все процессы перечисленные в depend_buject. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему алгоритму: |
# Получаем параметры процесса | # Получаем параметры процесса | ||
# Запускаем процесс | # Запускаем процесс | ||
− | # Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на | + | # Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на соответствующие очереди в redis |
# Ждем готовности процессов перечисленных в depend_buject. | # Ждем готовности процессов перечисленных в depend_buject. | ||
# Сообщаем о своей готовности. | # Сообщаем о своей готовности. | ||
# Запускаем основной цикл. | # Запускаем основной цикл. | ||
− | # Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в main_loop. | + | # Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в main_loop. |
− | + | ==Пользовательский интерфейс== | |
− | Пользовательский интерфейс | + | Пользовательские Веб интерфейсы хранятся в каталоге ui. При обращении к любой странице пользовательского интерфейса поднимается WebSocket позволяющий установить двухсторонний обмен сообщениями с браузером. Страница веб интерфейса становится еще одним процессом, который может обмениваться сообщениями с другими процессами. Каждая страница пользовательского интерфейса описывается в отдельном подкаталоге, и состоит как минимум из 2 файлов: |
− | Пользовательские Веб интерфейсы хранятся в каталоге ui. При обращении к любой странице пользовательского интерфейса поднимается WebSocket | ||
* [Имя страницы].html - разметка страницы. | * [Имя страницы].html - разметка страницы. | ||
* [Имя страницы].json - конфиг страницы, структура аналогична конфигу модулей. | * [Имя страницы].json - конфиг страницы, структура аналогична конфигу модулей. | ||
− | * [Имя страницы].py - не обязателен, модуль страницы где может быть описана серверная логика обработки событий | + | * [Имя страницы].py - не обязателен, модуль страницы где может быть описана серверная логика обработки событий. |
− | Пример разметки страницы | + | ===Пример разметки страницы=== |
− | <source lang= | + | <source lang=html5> |
<!DOCTYPE html> | <!DOCTYPE html> | ||
<html> | <html> | ||
Строка 249: | Строка 268: | ||
<!--bubot_socket - обязателен для каждой страницы - отвечает за обмен с сервером--> | <!--bubot_socket - обязателен для каждой страницы - отвечает за обмен с сервером--> | ||
<script type="text/javascript" src="/static/bubot_socket.js"></script> | <script type="text/javascript" src="/static/bubot_socket.js"></script> | ||
− | <!--организация скриптов и стилей на Ваше | + | <!--организация скриптов и стилей внутри страницы остается на Ваше усмотрение--> |
<script type="text/javascript" src="/ui/FAQ/FAQ.js"></script> | <script type="text/javascript" src="/ui/FAQ/FAQ.js"></script> | ||
<title>BuBot</title> | <title>BuBot</title> | ||
Строка 260: | Строка 279: | ||
</html> | </html> | ||
</source> | </source> | ||
− | Пример | + | |
+ | ===Пример логики страницы=== | ||
+ | В примере разметки страницы вся логика вынесена в FAQ.js Рассмотрим его в качестве примера: | ||
<source lang=javascript> | <source lang=javascript> | ||
function bubot_on_open() { | function bubot_on_open() { | ||
Строка 274: | Строка 295: | ||
// | // | ||
return { | return { | ||
− | on_get_play_list: function (data) { // получили с сервера список музыки и | + | on_get_play_list: function (data) { // получили с сервера список музыки и выводим его на странице |
var pl = $("#playlist"); | var pl = $("#playlist"); | ||
pl.empty(); | pl.empty(); | ||
Строка 282: | Строка 303: | ||
$(".playlist").mousedown(function(){ | $(".playlist").mousedown(function(){ | ||
// при клике на песне отправляем общее событие, если на него подписан сервис воспроизведения | // при клике на песне отправляем общее событие, если на него подписан сервис воспроизведения | ||
− | // то он | + | // то он запустит или остановит воспроизведение соответствующего трека |
bubot_send_message('send_event', {'event': "play", 'data': {'value': $(this).text()}}); | bubot_send_message('send_event', {'event': "play", 'data': {'value': $(this).text()}}); | ||
}); | }); | ||
Строка 290: | Строка 311: | ||
</source> | </source> | ||
− | Пример модуля страницы | + | ===Пример конфига страницы=== |
+ | <source lang=javascript> | ||
+ | </source> | ||
+ | |||
+ | ===Пример модуля страницы=== | ||
<source lang=python> | <source lang=python> | ||
import os | import os | ||
Строка 303: | Строка 328: | ||
_files = os.listdir("./playlist/") | _files = os.listdir("./playlist/") | ||
− | # все сообщения отправляемые на клиент обрабатываются там | + | # все сообщения отправляемые на клиент обрабатываются там одноименными функциями |
self.send_message_to_ui('on_get_play_list', {'param': _files}) | self.send_message_to_ui('on_get_play_list', {'param': _files}) | ||
Строка 319: | Строка 344: | ||
Ограничения: | Ограничения: | ||
− | Один экземпляр фреймворка позволяет разрабатывать любое количество роботов, но запустить можно только какого-то одного. Запуск на одном экземпляре фреймворка нескольких роботов существенно усложнил бы код фрейворка, поэтому я решил этого не делать. Если по какой то причине на одном железе есть необходимость запусть несколько роботов, то нужно просто сделать копию каталога с фрейворком или использовать символические ссылки на все каталоги кроме temp где находятся | + | Один экземпляр фреймворка позволяет разрабатывать любое количество роботов, но запустить можно только какого-то одного. Запуск на одном экземпляре фреймворка нескольких роботов существенно усложнил бы код фрейворка, поэтому я решил этого не делать. Если по какой то причине на одном железе есть необходимость запусть несколько роботов, то нужно просто сделать копию каталога с фрейворком или использовать символические ссылки на все каталоги кроме temp где находятся авто генерируемые файлы. |
[[Категория:Bubot]] | [[Категория:Bubot]] |
Текущая версия на 19:34, 4 апреля 2015
Данная статья является логическим продолжением обзорной статьи о фреймворке и предлагает более детальный обзор возможностей предоставляемых разработчику.
Самым важным при создании своего робота это является его проектирование - составление схемы будущих сервисов, и порядка их взаимодействия. Что в свою очередь не возможно без понимания возможностей фреймворка.
Как было сказано ранее, фреймворк представляет собой набор поддерживаемых пользователем модулей реализующих различные функции робототехники. При работе Bubot строит сеть из процессов, которые могут асинхронно получать и отправлять сообщения между собой. Где процесс - это запущенный с определенными параметрами экземпляр модуля.
Модуль это основной элементом фреймворка и свой обзор я начну именно с него. Модули фреймворка являются, в том числе и модулями питона, поэтому чтобы между ними не было путаницы в рамках фреймворка наши модули называются Buject-ами, находятся в каталоге buject и являются наследниками от одноименного класса или его потомков.
Базовый класс Buject предоставляет разработчику возможности хранения параметров в общей памяти, доступ к параметрам других модулей, систему обмена сообщениям.
Любой Buject состоит из двух файлов, один файл это собственно код на языке Python 3, а другой это конфиг модуля описывающий его назначение, параметры, методы и список зависимостей.
Содержание
Общая структура пользовательского модуля.
from buject.Buject import Buject
# импортируем необходимые модули питон, как минимум модуль базового класса
class BujectName(Buject):
# Buject Name - название Вашего класса может быть любым
# название не должно пересекаться с названиями других модулей и буботов
# В качестве базового класса может выступать как сам базовый модуль Buject
# так и любой из его потомков.
# Например для всех моторов создан класс Motor, который задает общий интерфейс для работы с данным типом устройств
# а для подключения мотора скажем через контроллер PCA9685 мы создаем свой модуль унаследованный от модуля Motor
# в котором реализуем интерфейс применительно к конкретному железу. Своеобразный драйвер устройства.
def __init__(self, user_config=None):
super(BujectName, self).__init__(user_config) # читаем конфиги
self.my_const_x = 'XXX' # определяете необходимые Вам константы.
# все настраиваемые параметры должны быть описаны в конфиге
def on_run(self):
# метод вызывается сразу после запуска процесса
return
def on_ready(self):
# метод вызывается перед запуском основного цикла.
# в этот момент все параметры сервиса инициализированы
# необходимые для старта процессы уже запустились
# процесс подписан на все необходимые сообщения
return
def main_loop(self):
# основной цикл процесса, частота срабатывания определяется параметром max_fps.
# отрицательное значение max_fps задает частоту не чаще одного раза в Х секунд
# метод автоматически один раз в мекунду обновляет параметры и статус модуля в разделяемой памяти
# большой fps существенно увеличивает нагрузку на процессор из-за redis
# если мощности Вашего процессора не хватает требуемый fps, то имеет смысл вынести этот алгоритм в систему реального времени,
# например на базе arduino, которой в свою очередь управлять из модуля.
return
def on_terminate(self):
# метод вызывается после остановки сервиса
return
def incoming_event_XXX(self, message):
# мeтод вызывается при поступлении события XXX
return
def incoming_request_XXX(self, message):
# мeтод вызывается при поступлении запроса XXX
return
def incoming_response_XXX(self, message):
# мeтод вызывается при поступлении ответа за запрос XXX
return
def action_XXX(self):
# Вы можете определить любые Ваши методы, хорошим тоном будет использование префикса,
# чтобы другие разработчики могли отличать пользовательские методы от методов фреймворка.
buject_config = self.config
# Вам доступны все параметры Вашего конфига в виде словаря
# для более удобного использования они продублированы в малых словарях
# param, status, bubot
bubot_param = self.bubot
buject_param = self.param
buject_status = self.status
# малые словари автоматически обновляются в разделяемой памяти и доступны другим сервисам
# особое внимание следует уделить служебным статусам характеризующим текущее состояние сервиса
buject_status = self.status['buject'] # статус ready, error, wait depend buject
buject_acton = self.status['action'] # чем в данный момент занимается сервис
self.send_status()
# принудительно обновление параметров и статусов сервиса в разделяемой памяти
self.get_status('bubot', 'buject')
# запрос параметров и статусов любого сервиса
# фреймворк предоставляет следующие мотоды для обмена информацией между сервисами
self.send_event('event_name', {'param_name':'param_value'}) #генерация события
self.send_request('request_name', {'param_name':'param_value'})
# отправка запроса, в метод передается только название и передаваемые параметры
# информацию о получателе запрос и названии запроса у получателя метод получит из конфига
self.send_response('request_data', 'response_data')
# отправка ответа на запрос,
# в метод передается запрос и данные для ответа
# запрос нужен для определения получателя ответа
return
Общая структура конфига пользовательского модуля.
{
"param": {
"name": {
"value": "BujectFAQ" // имя сервиса по умолчанию, обязателен
// если в системе будет запущен только один экземпляр этого модуля (сервис),
// то этого имени будет достаточно
},
"buject": {
"value": "BujectFAQ" // имя модуля, обязательно
},
"parent": {
"value": "Buject" // имя базового модуля, обязательно
},
"user_parameter_1": { // имя пользовательского параметра
"value": "default value", // значение по умолчанию
"description": "Пользовательский параметр 1", // описание параметра
"type": "int" // тип, используется при изменении значения параметра
// через пользовательский интерфейс.
// поддерживаются int (приведение строки из поля ввода к числу).
// если type отсутствует то введенное в поле ввода значение
// будет сохранено как строка
}
},
"status": { // список параметров характеризующих состояние сервиса
// в отличие от параметров они никак не влияют на работу сервиса
// их значение вычисляется в процессе работы сервиса
// характерным примером статуса является FPS
"user_status_1": { // имя пользовательского статуса
"value": "default value", // инициализируется значением по умолчанию
"description": "user status 1", // описание статуса
"type": "unixtime" // тип, используется при визуализации значения
// в пользовательском интерфейсе.
// поддерживаются unixtime (форматирование в строку при выводе)
// если type отсутствует то значение выводится как есть
}
},
"depend_buject": {// список необходимых для запуска модулей
"PCA9685": {} // например данный процесс не будет запущен пока не стартует
// процесс с именем PCA9685 (status.buject = 'ready')
},
"incoming_request": { // список входящих запросов которые может обрабатывать модуль
"set_power": { // название запроса
"description": "Установить мощность мотора", // описание
"param": { // список параметров принимаемых запросом, используется при отладке
// и конфигурировании робота через веб интерфейс
"param_name": { // состав и назначение аналогично param, см. выше
"value": "default value",
"description": "Пользовательский параметр 1",
"type": "int"
},
"response": 1 // указывается для информирования о том, что метода может быть направлен ответ
}
}
},
"outgoing_request": { // список запросов отправляемых сервисом
"set_power_move_motor": { // название запроса исходя из смысла действия, Установи мощность заднего мотора
"bubot": "other bubot", // указывается, если получателем запроса является другой бубот
"buject": "move_motor", // название сервиса получателя запроса, обязателен
"name": "set_power", // название запроса на стороне получателя, обязателен
"description": "Устанавливаем мощность заднего мотора",
"response": 1 // указывается если сервис будет ожидать ответа на этот запрос
}
},
"outgoing_event": { // список событий происходящих в модуле
"move_detected": { // название события
"description": "Обнаружено движение", // описание
"param": { // список параметров события, используется при отладке
// и конфигурировании робота через веб интерфейс - необязателен
"param_name": { // состав и назначение аналогично param, см. выше
"value": "default value",
"description": "Пользовательский параметр 1",
"type": "int"
}
}
}
},
"incoming_event": { // список событий которые необходимы сервису для работы
"room_move_detected": { // название события
"bubot": "other bubot", // указывается, если получателем запроса является другой бубот
"buject": "room_control", // название сервиса получателя запроса, обязателен
"name": "move_detected", // название сообщения на стороне инициатора
"description": "В комнате сработал датчик движения",
// если названия bubot и buject опущено, то имеется ввиду сообщения текущего бубота
"param": { // список параметров события, используется при отладке
// и конфигурировании робота через веб интерфейс - необязателен
"param_name": { // состав и назначение аналогично param, см. выше
"value": "default value",
"description": "Пользовательский параметр 1",
"type": "int"
}
}
}
}
В конфиге модуля можно указывать только те параметры или методы которые отличаются от его предков.
Плавно переходим к тому как запускаются модули, а если быть точнее - процессы. Один модуль можно запустить в неограниченном количестве экземпляров, например в случаях когда у нас несколько моторов или датчиков.
Список будущих процессов их параметры и взаимосвязи описываются в конфиге робота. По структуре он практически идентичен конфигу модуля. Рассмотрим его по подробнее.
Структура конфига робота.
{
"param": {
"name": {
"value": "BujectFAQ" // имя робота, должно быть равно имени файла
}
},
"depend_buject": {// список сервисов и их параметры которые будут запущены
"PCA9685": { // название сервиса, содержимое объекта содержит описание запускаемого процесса аналогично описанию модуля, указываем только параметры значения которых отличаются от значений по умолчанию
"param": { // следующий ниже следующий пример запустит на базе модуля BujectFAQ процесс с именем Test, в котором от значений по умолчанию будет отличаться только один user_parameter_1
"name": {"value": "Test"},
"buject": {"value": "BujectFAQ"},
"user_parameter_1": { "value": "33"}
}
}
},
"user": { // список пользователей имеющих доступ в веб-интерфейс, если раздел не указывать то доступ не контролируется
"Businka": { // Логин,
"password": "" // Пароль, можно не указывать
}
}
}
Конфиги роботов должны располагаться в каталоге config. В качестве примера можно посмотреть конфиг scout.
При запуске робота будут асинхронно запущены все процессы перечисленные в depend_buject. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему алгоритму:
- Получаем параметры процесса
- Запускаем процесс
- Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на соответствующие очереди в redis
- Ждем готовности процессов перечисленных в depend_buject.
- Сообщаем о своей готовности.
- Запускаем основной цикл.
- Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в main_loop.
Пользовательский интерфейс
Пользовательские Веб интерфейсы хранятся в каталоге ui. При обращении к любой странице пользовательского интерфейса поднимается WebSocket позволяющий установить двухсторонний обмен сообщениями с браузером. Страница веб интерфейса становится еще одним процессом, который может обмениваться сообщениями с другими процессами. Каждая страница пользовательского интерфейса описывается в отдельном подкаталоге, и состоит как минимум из 2 файлов:
- [Имя страницы].html - разметка страницы.
- [Имя страницы].json - конфиг страницы, структура аналогична конфигу модулей.
- [Имя страницы].py - не обязателен, модуль страницы где может быть описана серверная логика обработки событий.
Пример разметки страницы
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/jquery-ui-1.11.2/jquery-ui.min.css">
<script type="text/javascript" src="/static/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="/static/jquery-ui-1.11.2/jquery-ui.min.js"></script>
<!--bubot_socket - обязателен для каждой страницы - отвечает за обмен с сервером-->
<script type="text/javascript" src="/static/bubot_socket.js"></script>
<!--организация скриптов и стилей внутри страницы остается на Ваше усмотрение-->
<script type="text/javascript" src="/ui/FAQ/FAQ.js"></script>
<title>BuBot</title>
</head>
<body class="ui-widget-content">
<div id="playlist" class="ui-widget-content"></div>
<!--console - служебный див - при наличии в него выводится консоль-->
<div id="console" class="ui-widget-content"></div>
</body>
</html>
Пример логики страницы
В примере разметки страницы вся логика вынесена в FAQ.js Рассмотрим его в качестве примера:
function bubot_on_open() {
// функция вызывается после успешного соединение / пересоединения с сервером
// как правило здесь следует делать запросы к серверу на получение начальных данных
// в этом примере мы запрашиваем список музыкальных файлов
bubot_send_message('get_playlist', null);
}
function get_bubot_actions() {
// содержит список функций которые могут быть вызваны на клиенте по инициативе сервера
// например в данном примере таким способом приходит ответ на запрос get_playlist
// аналочисным способом строися подписка на события и запросы сервисов
//
return {
on_get_play_list: function (data) { // получили с сервера список музыки и выводим его на странице
var pl = $("#playlist");
pl.empty();
for (var elem in data) {
pl.append("<div id='PL_" + elem + "' class='playlist ui-widget-content'>" + data[elem] + "</div>");
}
$(".playlist").mousedown(function(){
// при клике на песне отправляем общее событие, если на него подписан сервис воспроизведения
// то он запустит или остановит воспроизведение соответствующего трека
bubot_send_message('send_event', {'event': "play", 'data': {'value': $(this).text()}});
});
}
}
}
Пример конфига страницы
Пример модуля страницы
import os
from engine.BubotWebSocket import BubotWebSocket
class FAQ(BubotWebSocket):
# все сообщения приходячщие из браузера обрабатываются
# одноименныи методами с префиксом ui_
def ui_get_playlist(self, param=None):
_files = os.listdir("./playlist/")
# все сообщения отправляемые на клиент обрабатываются там одноименными функциями
self.send_message_to_ui('on_get_play_list', {'param': _files})
def on_open(self):
# вызывается при установке соединения
return
def on_close(self):
# вызывается при разрыве соединения,
# например можно остановить или вернут машину назад
super().on_close() # обязательно вызываем родительский метод
Ограничения:
Один экземпляр фреймворка позволяет разрабатывать любое количество роботов, но запустить можно только какого-то одного. Запуск на одном экземпляре фреймворка нескольких роботов существенно усложнил бы код фрейворка, поэтому я решил этого не делать. Если по какой то причине на одном железе есть необходимость запусть несколько роботов, то нужно просто сделать копию каталога с фрейворком или использовать символические ссылки на все каталоги кроме temp где находятся авто генерируемые файлы.