Bubot в деталях — различия между версиями

Материал из razgovorov.ru
Перейти к: навигация, поиск
(Общая структура пользовательского модуля.)
(Пример логики страницы)
 
(не показано 12 промежуточных версий этого же участника)
Строка 1: Строка 1:
 
Данная статья является логическим продолжением [[Bubot|обзорной статьи]] о фреймворке и предлагает более детальный обзор возможностей предоставляемых разработчику.
 
Данная статья является логическим продолжением [[Bubot|обзорной статьи]] о фреймворке и предлагает более детальный обзор возможностей предоставляемых разработчику.
  
Самым важным при создании своего робота это является его проектирование - состаление схемы будущих сервисов, и порядка их взаимодействия. Что в свою очередь не возможно без понимания возможностей фреймворка.
+
Самым важным при создании своего робота это является его проектирование - составление схемы будущих сервисов, и порядка их взаимодействия. Что в свою очередь не возможно без понимания возможностей фреймворка.  
  
Как было сказано ранее, фреймворк представляет собой набор поддерживаемых пользователем модулей реализующих различные функции робототехники. При работе Bubot строит сеть из процессов, которые могут асинхронно получать и отправлять сообщения между собой. Где процесс - это запущенный с определенными параметрами экземпляр модуля.  
+
Как было сказано ранее, фреймворк представляет собой набор поддерживаемых пользователем модулей реализующих различные функции робототехники. При работе Bubot строит сеть из процессов, которые могут асинхронно  
 +
получать и отправлять сообщения между собой. Где процесс - это запущенный с определенными параметрами экземпляр модуля.  
  
Модуль является основным элементом фреймворка и свой обзор я начну именно с него. Модули фреймворка являются в том числе и модулями питона, но чтобы между ними не было путаницы в рамках фреймворка модули назваются Buject-ами находятся в каталоге buject и являются наследниками одноименного класса или его потомков.
+
Модуль это основной элементом фреймворка и свой обзор я начну именно с него. Модули фреймворка являются, в том числе и модулями питона, поэтому чтобы между ними не было путаницы в рамках фреймворка наши модули называются Buject-ами, находятся в каталоге buject и являются наследниками от одноименного класса или его потомков.  
 
 
Базовый класс Buject предоставляет разработчику возможности хранения параметров в общей памяти, доступ к параметрам других модулей, систему обмена сообщениям.
 
 
 
Любой Buject состоит из двух файлов, один файл это собственно код на языке Python 3, а другой это конфиг модуля описывающий его назначение, параметры, методы и  список зависимостей.
 
  
 +
Базовый класс Buject предоставляет разработчику возможности хранения параметров в общей памяти, доступ к параметрам других модулей, систему обмена сообщениям.
  
 +
Любой Buject состоит из двух файлов, один файл это собственно код на языке Python 3, а другой это конфиг модуля описывающий его назначение, параметры, методы и список зависимостей.
  
 
=== Общая структура пользовательского модуля. ===
 
=== Общая структура пользовательского модуля. ===
Строка 24: Строка 23:
 
# В качестве базового класса может выступать как сам базовый модуль Buject
 
# В качестве базового класса может выступать как сам базовый модуль Buject
 
# так и любой из его потомков.
 
# так и любой из его потомков.
# Например для всех моторов создан класс Motor, который задает общий интрефейс для работы с данным типом устройств
+
# Например для всех моторов создан класс Motor, который задает общий интерфейс для работы с данным типом устройств
# а для поддключения мотора скажем через контроллер PCA9685 мы создаем свой модуль унаследованный от модуля 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) # читаем конфиги
Строка 39: Строка 38:
  
 
     def on_ready(self):
 
     def on_ready(self):
         # метод вызвается перед запуском основного цикла.
+
         # метод вызывается перед запуском основного цикла.
 
         # в этот момент все параметры сервиса инициализированы
 
         # в этот момент все параметры сервиса инициализированы
 
         # необходимые для старта процессы уже запустились
 
         # необходимые для старта процессы уже запустились
Строка 49: Строка 48:
 
         # отрицательное значение max_fps задает частоту не чаще одного раза в Х секунд
 
         # отрицательное значение max_fps задает частоту не чаще одного раза в Х секунд
 
         # метод автоматически один раз в мекунду обновляет параметры и статус модуля в разделяемой памяти
 
         # метод автоматически один раз в мекунду обновляет параметры и статус модуля в разделяемой памяти
         # большой fps существенно увеличивает нагрузку на процессор из-зи redis
+
         # большой fps существенно увеличивает нагрузку на процессор из-за redis
         # если Вам нужен основной цикл с fps>30 имеет смысл вынести его в систему реального времени,
+
         # если мощности Вашего процессора не хватает требуемый fps, то имеет смысл вынести этот алгоритм в систему реального времени,
         # например на базе arduino, которой в свою очередь управлять из модуля
+
         # например на базе arduino, которой в свою очередь управлять из модуля.
 +
 
 +
       
 
         return
 
         return
  
 
     def on_terminate(self):
 
     def on_terminate(self):
         # метод вызвается после остановки сервиса
+
         # метод вызывается после остановки сервиса
 
         return
 
         return
  
Строка 71: Строка 72:
  
 
     def action_XXX(self):
 
     def action_XXX(self):
         # Вы можете опреелить любые Ваши методы, хорошим тоном будет использование префикса,
+
         # Вы можете определить любые Ваши методы, хорошим тоном будет использование префикса,
 
         # чтобы другие разработчики могли отличать пользовательские методы от методов фреймворка.
 
         # чтобы другие разработчики могли отличать пользовательские методы от методов фреймворка.
  
Строка 116: Строка 117:
 
         "name": {
 
         "name": {
 
             "value": "BujectFAQ" // имя сервиса по умолчанию, обязателен
 
             "value": "BujectFAQ" // имя сервиса по умолчанию, обязателен
             // если в системе булет запушен только один экземпляр этого модуля (сервис),
+
             // если в системе будет запущен только один экземпляр этого модуля (сервис),
 
             // то этого имени будет достаточно
 
             // то этого имени будет достаточно
 
         },
 
         },
Строка 128: Строка 129:
 
             "value": "default value", // значение по умолчанию
 
             "value": "default value", // значение по умолчанию
 
             "description": "Пользовательский параметр 1", // описание параметра
 
             "description": "Пользовательский параметр 1", // описание параметра
             "type": "int"  // тип, используется при изменениии значения параметра
+
             "type": "int"  // тип, используется при изменении значения параметра
 
             // через пользовательский интерфейс.
 
             // через пользовательский интерфейс.
 
             // поддерживаются int (приведение строки из поля ввода к числу).
 
             // поддерживаются int (приведение строки из поля ввода к числу).
Строка 142: Строка 143:
 
             "value": "default value", // инициализируется значением по умолчанию
 
             "value": "default value", // инициализируется значением по умолчанию
 
             "description": "user status 1", // описание статуса
 
             "description": "user status 1", // описание статуса
             "type": "unixtime"  // тип, используется при визуализаиии значения
+
             "type": "unixtime"  // тип, используется при визуализации значения
 
             // в пользовательском интерфейсе.
 
             // в пользовательском интерфейсе.
 
             // поддерживаются unixtime (форматирование в строку при выводе)
 
             // поддерживаются unixtime (форматирование в строку при выводе)
Строка 148: Строка 149:
 
         }
 
         }
 
     },
 
     },
     "depend_buject": {// список сервисов необходимых для запуска данного сервиса
+
     "depend_buject": {// список необходимых для запуска модулей
         "PCA9685": {} // напрмер данный сервис не будет запущен пока не стартует
+
         "PCA9685": {} // например данный процесс не будет запущен пока не стартует
        // сервис с именем PCA9685 (status.buject = 'ready'
+
                      // процесс с именем PCA9685 (status.buject = 'ready')
 
     },
 
     },
 
     "incoming_request": { // список входящих запросов которые может обрабатывать модуль
 
     "incoming_request": { // список входящих запросов которые может обрабатывать модуль
 
         "set_power": { // название запроса
 
         "set_power": { // название запроса
             "description": "Установить мощность мотораа", // описание
+
             "description": "Установить мощность мотора", // описание
 
             "param": { // список параметров принимаемых запросом, используется при отладке
 
             "param": { // список параметров принимаемых запросом, используется при отладке
 
                       // и конфигурировании робота через веб интерфейс
 
                       // и конфигурировании робота через веб интерфейс
Строка 162: Строка 163:
 
                     "type": "int"
 
                     "type": "int"
 
                 },
 
                 },
             "response": 1 // указывается для информацировании о том что метода может быть направлен ответ
+
             "response": 1 // указывается для информирования о том, что метода может быть направлен ответ
 
             }
 
             }
 
         }
 
         }
Строка 175: Строка 176:
 
         }
 
         }
 
     },
 
     },
     "outgoing_event": { // список событий происодящих в модуле
+
     "outgoing_event": { // список событий происходящих в модуле
 
         "move_detected": { // название события
 
         "move_detected": { // название события
 
             "description": "Обнаружено движение", // описание
 
             "description": "Обнаружено движение", // описание
Строка 192: Строка 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"
 +
                }
 
         }
 
         }
 
     }
 
     }
Строка 203: Строка 212:
 
Плавно переходим к тому как запускаются модули, а если быть точнее  - процессы. Один модуль можно запустить в неограниченном количестве экземпляров, например в случаях когда у нас несколько моторов или датчиков.
 
Плавно переходим к тому как запускаются модули, а если быть точнее  - процессы. Один модуль можно запустить в неограниченном количестве экземпляров, например в случаях когда у нас несколько моторов или датчиков.
  
Список будующих процессов их параметры и взаимосвязи описываются в конфиге робота. По структуре он практически идентичен конфигу модуля. Рассмотрим его по подробнее.
+
Список будущих процессов их параметры и взаимосвязи описываются в конфиге робота. По структуре он практически идентичен конфигу модуля. Рассмотрим его по подробнее.
  
 
===Структура конфига робота.===
 
===Структура конфига робота.===
Строка 215: Строка 224:
 
     "depend_buject": {// список сервисов и их параметры которые будут запущены
 
     "depend_buject": {// список сервисов и их параметры которые будут запущены
 
         "PCA9685": { // название сервиса, содержимое объекта содержит описание запускаемого процесса аналогично описанию модуля, указываем только параметры значения которых отличаются от значений по умолчанию
 
         "PCA9685": { // название сервиса, содержимое объекта содержит описание запускаемого процесса аналогично описанию модуля, указываем только параметры значения которых отличаются от значений по умолчанию
             "param": { // следующий ниже следующий пример запустит на базе модуля BujectFAQ процесс с именем Test, в котором от значиний по умолчанию будет отличаться только один user_parameter_1
+
             "param": { // следующий ниже следующий пример запустит на базе модуля BujectFAQ процесс с именем Test, в котором от значений по умолчанию будет отличаться только один user_parameter_1
 
                 "name": {"value": "Test"},
 
                 "name": {"value": "Test"},
 
                 "buject": {"value": "BujectFAQ"},
 
                 "buject": {"value": "BujectFAQ"},
Строка 222: Строка 231:
 
}  
 
}  
 
     },
 
     },
     "user": { // список пользователей имеющих доступ в веб-интерфейс
+
     "user": { // список пользователей имеющих доступ в веб-интерфейс, если раздел не указывать то доступ не контролируется
 +
        "Businka": { // Логин,
 +
            "password": "" // Пароль, можно не указывать
 +
        }
 
     }
 
     }
 
}
 
}
Строка 229: Строка 241:
 
Конфиги роботов должны располагаться в каталоге config. В качестве примера можно посмотреть конфиг scout.
 
Конфиги роботов должны располагаться в каталоге config. В качестве примера можно посмотреть конфиг scout.
  
При запуске робота будут асинхронно запущены все процессы перечисленные в depend_buject. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему алгориму:
+
При запуске робота будут асинхронно запущены все процессы перечисленные в depend_buject. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему алгоритму:
 
# Получаем параметры процесса
 
# Получаем параметры процесса
 
# Запускаем процесс
 
# Запускаем процесс
# Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на соответствущие очереди в redis
+
# Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на соответствующие очереди в redis
 
# Ждем готовности процессов перечисленных в depend_buject.
 
# Ждем готовности процессов перечисленных в depend_buject.
 
# Сообщаем о своей готовности.
 
# Сообщаем о своей готовности.
 
# Запускаем основной цикл.
 
# Запускаем основной цикл.
# Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в main_loop.  
+
# Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в main_loop.
 
 
  
 
==Пользовательский интерфейс==
 
==Пользовательский интерфейс==
Пользовательские Веб интерфейсы хранятся в каталоге ui. При обращении к любой странице пользовательского интерфейса поднимается WebSocket позволющий установить двухсторонний обмен сообщениями с браузером. Страница веб интерфеса становится еще одним процессом, который может обмениваться сообщениями с другими процессами. Каждая страница пользовательского интерфейса описывается в отдельном подкаталоге, и состоит как минимум из 2 файлов:
+
Пользовательские Веб интерфейсы хранятся в каталоге ui. При обращении к любой странице пользовательского интерфейса поднимается WebSocket позволяющий установить двухсторонний обмен сообщениями с браузером. Страница веб интерфейса становится еще одним процессом, который может обмениваться сообщениями с другими процессами. Каждая страница пользовательского интерфейса описывается в отдельном подкаталоге, и состоит как минимум из 2 файлов:
  
 
* [Имя страницы].html - разметка страницы.
 
* [Имя страницы].html - разметка страницы.
 
* [Имя страницы].json - конфиг страницы, структура аналогична конфигу модулей.
 
* [Имя страницы].json - конфиг страницы, структура аналогична конфигу модулей.
* [Имя страницы].py - не обязателен, модуль страницы где может быть описана серверная логика обработки событий. Данный модуль является наследуется от ожет содержать серверную логику по обработке команд пользовательского интерфейса.
+
* [Имя страницы].py - не обязателен, модуль страницы где может быть описана серверная логика обработки событий.
  
 
===Пример разметки страницы===
 
===Пример разметки страницы===
Строка 257: Строка 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>
Строка 269: Строка 280:
 
</source>
 
</source>
  
===Пример конфига страницы===
+
===Пример логики страницы===
 +
В примере разметки страницы вся логика вынесена в FAQ.js Рассмотрим его в качестве примера:
 
<source lang=javascript>
 
<source lang=javascript>
 
function bubot_on_open() {
 
function bubot_on_open() {
Строка 283: Строка 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();
Строка 291: Строка 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()}});
 
             });
 
             });
Строка 297: Строка 309:
 
     }
 
     }
 
}
 
}
 +
</source>
 +
 +
===Пример конфига страницы===
 +
<source lang=javascript>
 
</source>
 
</source>
  
Строка 312: Строка 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})
  
Строка 328: Строка 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. Основной процесс будет дожидаться их запуска. Каждый из процессов будет запускаться по следующему алгоритму:

  1. Получаем параметры процесса
  2. Запускаем процесс
  3. Проверяем наличие методов для обработки входящих запросов и событий, оформляем подписку на соответствующие очереди в redis
  4. Ждем готовности процессов перечисленных в depend_buject.
  5. Сообщаем о своей готовности.
  6. Запускаем основной цикл.
  7. Каждая итерация цикла начинается с последовательной обработки всей очереди сообщений накопившейся с предыдущей итерации, после чего управление передается в 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 где находятся авто генерируемые файлы.