Pull to refresh

Программирование Pocketbook’ов

С брендом Pocketbook, думаю, знакомы многие. Если нет, поищите по тэгу pocketbook. Кратко, это устройство с экраном e-ink, использующееся для чтения электронных книг и работающее под управлением Linux. Два дня назад я купил себе такой – Pocketbook 301 plus. Одна из причин выбора именно Pocketbook в том, что для него есть SDK. Наверное, каждый программист наслаждается возможностью расширять функционал своих гаджетов под свои же нужды.

Программы для устройств Pocketbook пишутся на C с использование компилятора GCC. О том, как программировать на C под Linux, материалов в интернете масса, а вот об использовании библиотеки InkView почти ничего. InkView – это библиотека ввода\вывода для устройств Pocketbook. Учебный материал об этой библиотеке ограничивается заголовочным файлом inkview.h и исходником inkdemo.c. Несмотря на 10-и летний стаж программирования, я очень не люблю разбираться в чём-то по чужим исходникам. Уверен, я не один такой. И вполне возможно, что смогу своей статьёй облегчить кому-нибудь старт.

Мы начнём с простого – минимальной запускающейся программы, а потом будем её поэтапно усложнять. Цель этой статьи показать основные принципы, поэтому ничего полезного мы пока не напишем. Возможно, в будущем хабравчане подкинут мне идею полезной программы, и мы её вместе напишем или, если это будет интереснее, мы исследуем и разберём каждую упомянутую в inkview.h функцию и у Pocketbook SDK наконец-то появится документация.


Но сначала подготовим среду разработки. Скачайте PBSDK версии 14.2. Не знаю, почему так, но на момент написания статьи, программы, собранные с использование PBSDK версии 15.1, не запускались на устройстве. Дистрибутив PBSDK – это самораспаковывающийся архив. Он предложит распаковаться в C:\PBSDK, я с ним согласился, чего и вам советую. В процессе распаковки он сам установит нужные библиотеки, переменные окружения и произведёт изменения в реестре. Можно приступать к программированию.

В C:\PBSDK\sources создайте папку… Пусть будет hbr01. А в ней, в свою очередь, папки src и images и скопируйте из C:\PBSDK\sources\inkdemo файлы make.bat и makearm.bat. Первый собирает «эмулируемый» файл программы, а второй исполняемый файл для устройства. В папке src будут храниться исходные файлы нашей программы, а папка images нам пригодится ближе к концу статьи. Ещё нам понадобиться сделать небольшое изменение в bat-файлах. В файле make.bat найдите строку set OUTPUT=inkdemo.exe и замените на set OUTPUT=hbr01.exe, также для makearm.bat найдите set OUTPUT=hbr01.app и замените на set OUTPUT=hbr02.app. Ну, и наконец, в папке src создаём файл main.c следующего содержания:
Copy Source | Copy HTML
  1. #include "inkview.h"
  2. int main_handler(int type, int par1, int par2) {
  3.     if (type == EVT_KEYPRESS) {
  4.         CloseApp();
  5.     }
  6.     return  0;
  7. }
  8.  
  9. int main(int argc, char **argv) {
  10.     InkViewMain(main_handler);
  11.     return  0;
  12. }


Попробуйте запустить make.bat, в папке должен появиться файл hbr01.exe. Если вы его запустите, то увидите монотонное окно, закрывающееся при нажатии любой клавиши.

image

Разберёмся, как это работает. В main мы вызываем функцию InkViewMain. Она, как и многие другие, определена в заголовочном файле inkview.h, поэтому мы его должны предварительно включить. InkViewMain принимает только один параметр – функцию — обработчик событий, main_handler. Его можно считать сердцем программы. Функция main_handler принимает три параметра: type – идентификатор события и два параметра события. К параметрам события мы вернёмся позже. Пока же мы проверяем тип события и, если это нажатие клавиши, то завершаем работу программы функцией CloseApp.

Программа получилась предельно скучной. Это даже на классический “Hello, World!” не тянет. Добавим немного интерактивности, а именно меню и всплывающее сообщение:
Copy Source | Copy HTML
  1. #include "inkview.h"
  2.  
  3. int cindex= 0;
  4.  
  5. static imenu menu1[] = {
  6.     { ITEM_HEADER,  0, "Menu", NULL },
  7.     { ITEM_ACTIVE, 101, "Say 'Hi'", NULL },
  8.     { ITEM_ACTIVE, 102, "Exit", NULL },
  9.     {  0,  0, NULL, NULL }
  10. };
  11.  
  12. void menu1_handler(int index) {
  13.     cindex = index;
  14.  
  15.     switch (index) {
  16.         case 101:
  17.             Message(ICON_INFORMATION, "Message", "Hello, World!\n"<br/>                "This message will disappear after 5 seconds, or press any key", 5000);
  18.             break;
  19.  
  20.         case 102:
  21.             CloseApp();
  22.             break;
  23.     }
  24. }
  25.  
  26. int main_handler(int type, int par1, int par2) {
  27.     if (type == EVT_KEYPRESS) {
  28.         switch (par1) {
  29.             case KEY_OK:
  30.                 OpenMenu(menu1, cindex, 20, 20, menu1_handler);
  31.                 break;
  32.  
  33.             case KEY_BACK:
  34.                 CloseApp();
  35.                 break;
  36.         }
  37.     }
  38.     return  0;
  39. }
  40.  
  41. int main(int argc, char **argv) {
  42.     InkViewMain(main_handler);
  43.     return  0;
  44. }


Как и в предыдущем исходном файле, мы подключаем inkview.h и вызываем в main функцию InkViewMain, но main_handler у нас усложнился. Проверив, что событие – это нажатие клавиши, мы проверяем первый параметр события, чтобы узнать какая это клавиша. Если это клавиша BACK (в эмуляции F3), то завершаем программу уже известной нам функцией CloseApp. Если же это клавиша OK (в эмуляции Enter), то рисуем меню, вызывая функцию OpenMenu. Функция OpenMenu принимает 5 параметров: массив структур imenu, текущий выбранный элемент меню, координаты верхнего левого угла меню на экране и обработчик меню. Структура imenu имеет следующие поля: тип элемента меню, идентификатор элемента меню, заголовок элемента меню, дочернее меню. У нас последнего пункта пока нет, поэтому мы задаём NULL. Самое интересное – это функция menu_handler. Она принимает только один параметр – идентификатор выбранного элемента меню. Самое первое, что мы делаем в этой функции – это сохраняем идентификатор выбранного элемента меню в заранее объявленной переменной cindex, чтобы при следующем открытии меню курсор стоял в той же позиции. Для нашего крошечного меню это не обязательно, но вообще это хорошая практика, облегчающая пользователю управление устройством с ограниченными элементами управления. Вежливыми побыли, пора быть полезными, проверяем, что же выбрал пользователь. Если пользователь выбрал пункт меню Exit с идентификатором 102, завершаем приложение всё той же функцией CloseApp. Если пользователь хочет поздороваться, покажем ему сообщение с помощью функции Message. Мы передаём функции константу ICON_INFORMATION, чтобы в сообщении отобразился соответствующий значок, строку заголовка функции, строку сообщения и количество миллисекунд, через которое сообщение самостоятельно закроется.

image
image

Вот мы и научились разговаривать с пользователем. Теперь надо научиться задавать ему вопросы:
Copy Source | Copy HTML
  1. #include "inkview.h"
  2.  
  3. int cindex= 0;
  4.  
  5. static imenu menu1_submenu1[] = {
  6.     { ITEM_ACTIVE, 103, "Ask about weather", NULL },
  7.     {  0,  0, NULL, NULL }
  8. };
  9.  
  10. static imenu menu1[] = {
  11.     { ITEM_HEADER,  0, "Menu", NULL },
  12.     { ITEM_ACTIVE, 101, "Say 'Hi'", NULL },
  13.     { ITEM_SUBMENU,  0, "Submenu", menu1_submenu1 },
  14.     { ITEM_SEPARATOR,  0, NULL, NULL },
  15.     { ITEM_ACTIVE, 102, "Exit", NULL },
  16.     {  0,  0, NULL, NULL }
  17. };
  18.  
  19. void dialog_handler(int button) {
  20.  
  21. }
  22.  
  23. void menu1_handler(int index) {
  24.     cindex = index;
  25.  
  26.     switch (index) {
  27.         case 101:
  28.             Message(ICON_INFORMATION, "Message", "Hello, Worl!\n"<br/>                "This message will disappear after 5 seconds, or press any key", 5000);
  29.             break;
  30.  
  31.         case 102:
  32.             CloseApp();
  33.             break;
  34.  
  35.         case 103:
  36.             Dialog(ICON_QUESTION, "Dialog", "It's a nice day today,isn't it?\n", "Yes", "No", dialog_handler);
  37.             break;
  38.     }
  39. }
  40.  
  41. int main_handler(int type, int par1, int par2) {
  42.     if (type == EVT_KEYPRESS) {
  43.         switch (par1) {
  44.             case KEY_OK:
  45.                 OpenMenu(menu1, cindex, 20, 20, menu1_handler);
  46.                 break;
  47.  
  48.             case KEY_BACK:
  49.                 CloseApp();
  50.                 break;
  51.         }
  52.     }
  53.     return  0;
  54. }
  55.  
  56. int main(int argc, char **argv) {
  57.     InkViewMain(main_handler);
  58.     return  0;
  59. }
  60.  


Вопросы задавать мы будем с помощью функции Dialog. Она очень похожа на функцию Message, так же принимает константу иконки, строку заголовка и строку сообщения, но вместо таймаута эта функция принимает две строки названий кнопок и обработчик диалога. Вместо любой из строк “Yes” и “No” вы можете передать NULL, тогда кнопка будет одна. Или вы можете вместо обеих строк передать NULL, тогда это будет самый бесполезный диалог в мире. Впрочем, наш сейчас тоже бесполезен, так как функция обработчика ничего не делает. К этому мы вернёмся чуть позже, а пока посмотрим, как вызывается диалог. Мы определили ещё один массив структур imenumenu1_submenu1. И мы немного изменили menu1. В menu1 добавился элемент с типом ITEM_SUBMENU и элемент с типом ITEM_SEPARATOR. Думаю, их назначение понятно без дополнительных объяснений, достаточно посмотреть программу в работе.

image
image

А теперь попробуем определить, что ответил пользователь. Это просто, надо проверить параметр button, который принимает dialog_handler, и отреагировать. Проверить просто, отреагировать чуть сложнее. Можно, конечно, вывести Message, соответствующий нажатой кнопке, но выводить сообщения мы уже умеем, а вот писать на экране нет. Этим и займёмся. Добавьте в исходник, сразу после int cindex=0; строку ifont *arialb12; — это указатель на шрифт, который мы будем использовать. Перед использованием его надо инициализировать, для этого добавим в main_handler блок:
Copy Source | Copy HTML
  1. if (type == EVT_INIT) {
  2.     arialb12 = OpenFont("LiberationSans", 12, 1);
  3. }
  4.  


Событие EVT_INIT происходит один раз, при запуске приложения. Хорошее место для инициализации ресурсов. Как показывает опыт, если где-то есть «конструктор», то должен быть и «деструктор». Антагонистом события EVT_INIT является событие EVT_EXIT. Как понятно из названия, оно возникает при завершении работы приложения. Но вернёмся к инициализации. Функция OpenFont, принимает три параметра: имя шрифта, размер и… Я не знаю что это, честно. Тем не менее, несмотря на то, что назначение третьего параметра неясно, шрифт мы инициализировали и можем теперь его использовать. Добавим к нашему исходнику ещё одну функцию:
Copy Source | Copy HTML
  1. void msg(char *s) {
  2.     FillArea(350, 770, 250, 20, WHITE);
  3.     SetFont(arialb12, BLACK);
  4.     DrawString(350, 770, s);
  5.     PartialUpdate(350, 770, 250, 20);
  6. }


Она и будет писать на экране то, что мы передадим ей параметром. Для этого мы с помощью функции FillArea рисуем белый прямоугольник длинной 250 и высоток 20 в точке с координатами 350 по X и 770 по Y, функцией SetFont устанавливаем, предварительно инициализированный, шрифт, который будет использоваться, функцией DrawString рисуем строку и функцией PartialUpdate даём контроллеру экрана команду обновить область, в которой предварительно нарисовали прямоугольник. Работая с экранами e-ink, надо всегда помнить, что единожды выведенное никуда с него не денется. Всё надо стирать самостоятельно.

А теперь пришло время новую функцию msg использовать по назначению. Измените dialog_handler:
Copy Source | Copy HTML
  1. void dialog_handler(int button) {
  2.     msg(button == 1 ? "Choosed: yes" : "Choosed: no");
  3. }


Всё вместе должно выглядит так:
Copy Source | Copy HTML
  1. #include "inkview.h"
  2.  
  3. int cindex= 0;
  4. ifont *arialb12;
  5.  
  6. static imenu menu1_submenu1[] = {
  7.     { ITEM_ACTIVE, 103, "Ask about weather", NULL },
  8.     {  0,  0, NULL, NULL }
  9. };
  10.  
  11. static imenu menu1[] = {
  12.     { ITEM_HEADER,  0, "Menu", NULL },
  13.     { ITEM_ACTIVE, 101, "Say 'Hi'", NULL },
  14.     { ITEM_SUBMENU,  0, "Submenu", menu1_submenu1 },
  15.     { ITEM_SEPARATOR,  0, NULL, NULL },
  16.     { ITEM_ACTIVE, 102, "Exit", NULL },
  17.     {  0,  0, NULL, NULL }
  18. };
  19.  
  20. void msg(char *s) {
  21.     FillArea(350, 770, 250, 20, WHITE);
  22.     SetFont(arialb12, BLACK);
  23.     DrawString(350, 770, s);
  24.     PartialUpdate(350, 770, 250, 20);
  25. }
  26.  
  27. void dialog_handler(int button) {
  28.     msg(button == 1 ? "Choosed: yes" : "Choosed: no");
  29. }
  30.  
  31. void menu1_handler(int index) {
  32.     cindex = index;
  33.  
  34.     switch (index) {
  35.         case 101:
  36.             Message(ICON_INFORMATION, "Message", "Hello, Worl!\n"<br/>                "This message will disappear after 5 seconds, or press any key", 5000);
  37.             break;
  38.  
  39.         case 102:
  40.             CloseApp();
  41.             break;
  42.  
  43.         case 103:
  44.             Dialog(ICON_QUESTION, "Dialog", "It's a nice day today,isn't it?\n", "Yes", "No", dialog_handler);
  45.             break;
  46.     }
  47. }
  48.  
  49. int main_handler(int type, int par1, int par2) {
  50.     if (type == EVT_INIT) {
  51.         arialb12 = OpenFont("LiberationSans", 12, 1);
  52.     }
  53.  
  54.     if (type == EVT_KEYPRESS) {
  55.         switch (par1) {
  56.             case KEY_OK:
  57.                 OpenMenu(menu1, cindex, 20, 20, menu1_handler);
  58.                 break;
  59.  
  60.             case KEY_BACK:
  61.                 CloseApp();
  62.                 break;
  63.         }
  64.     }
  65.     return  0;
  66. }
  67.  
  68. int main(int argc, char **argv) {
  69.     InkViewMain(main_handler);
  70.     return  0;
  71. }


Теперь мы умеем обрабатывать события, использовать меню, показывать сообщения, выводить диалоговые окна, писать на экране. Рисовать на экране не умеем. Давайте его немного украсим логотипом Habrahabr'ы.
Работа с изображениями немножко чудная. Для начала нам понадобиться конвертировать логотип в поддерживаемый формат – точечный рисунок в 16-и оттенках серого. Потом надо положить его в заранее созданную папку images. В процессе сборки, рисунок будет конвертирован утилитой pbres в объектный файл и слинкован вместе с программой. Посмотрим, как с ним можно после этого работать:
Copy Source | Copy HTML
  1. #include "inkview.h"
  2.  
  3. int cindex= 0;
  4. ifont *arialb12;
  5. extern const ibitmap habrahabr;
  6.  
  7. static imenu menu1_submenu1[] = {
  8.     { ITEM_ACTIVE, 103, "Ask about weather", NULL },
  9.     {  0,  0, NULL, NULL }
  10. };
  11.  
  12. static imenu menu1[] = {
  13.     { ITEM_HEADER,  0, "Menu", NULL },
  14.     { ITEM_ACTIVE, 101, "Say 'Hi'", NULL },
  15.     { ITEM_SUBMENU,  0, "Submenu", menu1_submenu1 },
  16.     { ITEM_SEPARATOR,  0, NULL, NULL },
  17.     { ITEM_ACTIVE, 102, "Exit", NULL },
  18.     {  0,  0, NULL, NULL }
  19. };
  20.  
  21. void msg(char *s) {
  22.     FillArea(350, 770, 250, 20, WHITE);
  23.     SetFont(arialb12, BLACK);
  24.     DrawString(350, 770, s);
  25.     PartialUpdate(350, 770, 250, 20);
  26. }
  27.  
  28. void dialog_handler(int button) {
  29.     msg(button == 1 ? "Choosed: yes" : "Choosed: no");
  30. }
  31.  
  32. void menu1_handler(int index) {
  33.     cindex = index;
  34.  
  35.     switch (index) {
  36.         case 101:
  37.             Message(ICON_INFORMATION, "Message", "Hello, Worl!\n"<br/>                "This message will disappear after 5 seconds, or press any key", 5000);
  38.             break;
  39.  
  40.         case 102:
  41.             CloseApp();
  42.             break;
  43.  
  44.         case 103:
  45.             Dialog(ICON_QUESTION, "Dialog", "It's a nice day today,isn't it?\n", "Yes", "No", dialog_handler);
  46.             break;
  47.     }
  48. }
  49.  
  50. int main_handler(int type, int par1, int par2) {
  51.     if (type == EVT_INIT) {
  52.         arialb12 = OpenFont("LiberationSans", 12, 1);
  53.     }
  54.  
  55.     if (type == EVT_SHOW) {
  56.         DrawBitmap(120, 30, &habrahabr);
  57.         FullUpdate();
  58.     }
  59.  
  60.     if (type == EVT_KEYPRESS) {
  61.         switch (par1) {
  62.             case KEY_OK:
  63.                 OpenMenu(menu1, cindex, 20, 20, menu1_handler);
  64.                 break;
  65.  
  66.             case KEY_BACK:
  67.                 CloseApp();
  68.                 break;
  69.         }
  70.     }
  71.     return  0;
  72. }
  73.  
  74. int main(int argc, char **argv) {
  75.     InkViewMain(main_handler);
  76.     return  0;
  77. }


В пятой строке мы объявляем наше изображение. В main_handler добавляем проверку на событие EVT_SHOW. Это событие сопутствует вообще всем событиям, проще говоря, если произошёл вызов main_handler, можно гарантировать, что EVT_SHOW произошло. Самое лучшее место, чтобы что-нибудь рисовать. Функция DrawBitmap, которой мы и рисуем изображения на экране, принимает три параметра: координаты на экране и ссылку на изображение. После этого необходимо обновить экран. Ради эксперимента можете попробовать закомментировать функцию FullUpdate.

image

Вот и всё. Теперь можно запустить makearm.bat, закинуть получившийся hbr01.app в папку /system/games/ на устройстве и насладиться результатом. Результат небольшой, но он меня радует. Теперь я могу добавить необходимую функциональность не только своему ПК и КПК, но и Pocketbook'у. Мне остаётся только ждать программируемую женщину и придумывать, как расковырять борткомпьютер своего автомобиля.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.