Linux на борту: Разработка приложений для Nokia N810

Используем сигнальный API-интерфейс

Сигнальный интерфейс Nokia N810 позволяют разработчикам просто управлять сигналами на программном уровне. Питер Сибах демонстрирует, как небольшая программа с интерфейсом командной строки может использовать этот API для различных полезных целей.

Питер Сибах, автор, Независимый

Питер Сибах (Peter Seebach) работает с компьютерами много лет и постепенно приспособился. Хотя он до сих пор не понимает, почему мышку надо чистить так часто.



03.02.2009

Самым серьезным ограничением Nokia 770 как потенциальной замены КПК было отсутствие возможности каким-либо образом настроить загрузку по расписанию. В N800 появился намного усовершенствованный сигнальный интерфейс, который перешел в новый N810. В этой статье я обращаюсь к сигнальному API-интерфейсу языка С и предлагаю интерфейс, позволяющих сделать его доступным для shell-скриптов и программ на других языках.

Для начала короткий обзор N810. N810 – это КПК с экраном 800x480. Он оснащен интерфейсами Bluetooth, Wi-Fi и USB. Используется ядро Linux® версии 2.6.21, адаптированное для соответствующей аппаратной платформы.

N810 очень напоминает предыдущую модель N800. Среди новшеств – модуль GPS и встроенная клавиатура. Единственная причина, по которой N810 может показаться менее удобным, чем N800, - это наличие только одного слота для карт памяти MMC/SD. N800 был оснащен двумя полноразмерными слотами MMC/SD, доступными для пользователя. N810 оснащен встроенной картой емкостью 2 ГБ во «внутреннем слоте», а также слотом miniSD для сменных носителей. (Это может слегка разочаровать, если вы, как и я, собрали уйму SD-карт за несколько лет работы с встраиваемыми системами.)

Среда разработки для N810 в основном та же, что и для N800 (см. другие статьи о 770 и N800). Были обновлены инструментарий Scratchbox и среда maemo, но основные процессы не изменились, а установка Scratchbox и SDK осталась простой и быстрой. Для разработчика имеют значение два основных изменения. Во-первых, можно установить внешний xterm, что является крупным усовершенствованием. Во-вторых, при установке пакета openssh-server вам предлагается изменить пароль суперпользователя root. Это большой шаг по сравнению с предыдущей версией, когда по умолчанию паролем root был «rootme». Естественно, вам нужно придумать какой-нибудь другой пароль.

Сигнальный API

Сигнальный API-интерфейс появился в maemo 3.0 в прошлом году. Он представляет собой набор вызовов для взаимодействия с сигнальным демоном, который поддерживает сигнальные службы. Лучше использовать этот интерфейс вместо попыток написать собственный, и настоятельно рекомендуется избегать написания собственных таймеров в средах, подобных N810. Управление питанием во встраиваемых системах – сложный вопрос, и здесь легко допустить ошибку; вместо ручной работы лучше воспользоваться готовым кодом.

Даже если ваш сигнальный код написан правильно и элегантно, централизованный сервис имеет неоспоримое преимущество. Представьте, что вы написали замечательный сигнальный интерфейс, который изредка пробуждает систему, например, каждые пять минут или что-то около того. Это почти не повлияет на заряд батареи. Теперь представьте, что другие люди, настолько же опытные, как и вы, создадут похожие интерфейсы. Множество установленных интерфейсов будут пробуждать устройство несколько раз в течение каждых пяти минут. Еще хуже то, что другие пользователи могут выбрать другие интервалы. Итак, у вас есть одна программа, которая пробуждает устройство каждые три минуты, другая – каждые пять минут, и третья – каждые семь минут, или еще хуже: пара сигналов может запустить независимые циклы пробуждения и засыпания устройства. Поэтому лучше использовать стандартный API.

Сигнальный API способен на многое. В сущности, с его помощью можно весьма гибко настраивать свойства назначенных заданий. Задание может отображаться или не отображаться для пользователя. Задания способны выполнять три основных действия: отображать сообщения, запускать программы и отправлять сообщения через шину D-Bus другим приложениям. Хотя с обычной точки зрения они не рассматриваются в качестве сигналов, они имеют прочную функциональную связь с обычными напоминаниями, которые отображает календарь. Иначе говоря, если уж вы реализуете функциональность сигналов для поддержки календаря и тому подобных вещей, лучше сделать немного больше и обрабатывать все сразу.

Сигналы хранятся в файле /var/lib/alarmd/alarm_queue.xml. Это файл более-менее читаем, но содержит большое количество кодированных флагов. Можно много узнать о компонентах структуры сигнальных событий, создавая события при помощи других приложений, а затем читая изменения, внесенные в этот файл.

Иногда люди предпочитают создавать собственные парсеры аргументов. (Я тоже грешен – однажды написал весьма функциональный аналог getopt() ().) Однако в среде UNIX® в девяти случаях из десяти вам придется использовать getopt(). Он имеет ясную и привычную семантику и оправдывает ожидания пользователей. В нем есть множество функций необходимых параметров для большинства программ. Также он упрощает код и устраняет ошибки. Раз за разом я убеждался, что программы, созданные при помощи собственного парсера аргументов, содержат ошибки. Я написал собственный в 1997 году и думаю, что в нем до сих пор остались ошибки.

Программируете ли вы в shell, на C или на каком-либо другом языке, - используйте функциональность getopt. (В оболочке shell POSIX-ответом будет встроенный в оболочку getopts, который лучше интегрирован в shell, чем программа getopt.)

Простое напоминание

Для краткости я пропущу часть описания сигнального интерфейса, относящуюся к шине D-Bus, и сразу перейду к функциям сообщений и запуска программ, просто потому, что они не требуют использования кода D-Bus для обработки входящих уведомлений. Было бы замечательно иметь возможность создавать простые напоминания о событиях при помощи командной строки. Также было бы неплохо создавать произвольные сигнальные события для командной строки, которые будут запускать программы или отображать сообщения.

Система сигналов чрезвычайно гибка. У сигнала есть начальное время, параметр, указывающий частоту повторения (только в минутах) и количество повторений. Такой набор не вполне универсален; например, при его помощи нельзя задать еженедельную встречу в одно и тоже время (поскольку при переходе на летнее время его настройки будут сбрасываться). В качестве напоминания можно назначить звук, изображение или текстовое сообщение.

Также существует несколько флагов, управляющих свойствами сигналов. Эти флаги представляют собой простые булевы флаги; если флаг не задан явно, используется параметр, противоположный по значению. Набор отдельных флагов в сочетании с небольшой коллекцией значений параметров отлично подходят для getopt(), но вначале нам надо подумать о том, какие флаги нам понадобятся и где их хранить. Поскольку флаги точно соответствуют структуре сигналов, с нее я и начну.

Для начала нам понадобится пустая структура alarm_event_t. В одном из примеров программ maemo эта структура обнуляется при помощи memset(), однако, строго говоря, это неправильно, так как использование memset для заполнения чего-либо нулями не гарантирует обнуления указателей (хотя в данной системе это работает). Обычный метод taketh здесь недоступен, однако существует метод giveth: вы можете использовать инциализатор нулевого значения, чтобы назначить значение первого поля, и всем последующим полям присваивается соответствующее значение, как если бы явно был задан 0, что гарантирует создание нулевых указателей.

Листинг 1. Обнуляем наше событие
alarm_event_t event = { 0 };

Теперь нам необходимо соответственно задать значения элементов. Самым сложным, конечно, будет время. Люди, которые свободно указывают время напоминания в секундах с начала эпохи UNIX, вероятно, не нуждаются в органайзере. Поэтому придется немного подумать. Отрывок кода ниже обрабатывает три наиболее распространенных формата времени.

Листинг 2. Ну и на сколько вы хотите завести будильник?
time_t
parse_time(char *s) {
        int Y, M, D, h, m;
        time_t secs = time(NULL);
        struct tm t = *(localtime(&secs));

        if (sscanf(s, "%d-%d-%d %d:%d", &Y, &M, &D, &h, &m) == 5) {
                if (Y < 100) {
                        t.tm_year = (t.tm_year - (t.tm_year % 100)) + Y;
                } else {
                        t.tm_year = Y - 1900;
                }
                t.tm_mon = M - 1;
                t.tm_mday = D;
                t.tm_hour = h;
                t.tm_min = m;
        } else if (sscanf(s, "%d:%d", &h, &m) == 2) {
                t.tm_hour = h;
                t.tm_min = m;
        } else {
                m = strtol(s, &s, 10);
                if (*s || !m) {
                        usage("enter '[YYYY-MM-DD] hh:mm' or delay in minutes.");
                }
                t.tm_min += m;
        }
        return mktime(&t);
}

Этот пример охватывает самые распространенные форматы дат: даты в формате ISO (с указанием года), время дня или интервал в минутах. Не поддерживается 12-тичасовой стандарт времени, а также простой ввод числа и месяца. Во-первых, из-за того, что сложно угадать, в каком порядке пользователь будет их вводить, а во-вторых, – потому, что мне лень.

Для обработки большинства параметров командной строки просто используйте стандартную функцию getopt() (подробности см. врезке). Команда getopt() обрабатывает заданные аргументы (вы должны передать их argc и argv) в соответствии с указанными параметрами. Допускаются только параметры, состоящие из одного символа; каждый символ в строке соответствует параметру программы. Чтобы задать несколько параметров, символы указываются через запятую. Например, строка ab: обозначает, что программа запускается с двумя параметрами -a и -b, а слово, следующее за -b , обозначает дополнительный аргумент. Взгляните, как будет выглядеть код:

Листинг 3. Часть списка параметров
while ((o = getopt(argc, argv, "ABDIZc:C:i:nr:R:s:t:z:")) != -1) {
        switch (o) {
        case 'A':
                event.flags |= ALARM_EVENT_ACTDEAD;
                break;
        [...]
        case 'c':
                event.exec_name = strdup(optarg);
                break;
        [...]
        case '?':
        default:
                usage("unknown argument -%c.", optopt);
                break;
        }
}

Если вы не очень хорошо знакомы с getopt(), вам понадобится некоторое объяснение. При отсутствии дополнительных параметров getopt() возвращает -1. В противном случае возвращается значение следующего флага. Если флаг соответствует аргументу, последний сохраняется в глобальной переменной optarg. Вопросительный знак указывает на неверный параметр; в этом случае символ, вызвавший его появление, сохраняется в переменной optopt. На самом деле приведенное выше сообщение об ошибке некорректно, оно также могло бы быть вызвано ссылкой параметра на отсутствующий аргумент. Тем не менее, этого не может произойти при корректном вызове нашей программы, которая требует дополнительных аргументов.

После того как getopt() возвратила -1, глобальная переменная optind содержит индекс первого аргумента, не являвшегося частью параметров командной строки. Остается обработать две вещи: назначенное время (первый аргумент после параметров) и сообщение (оставшийся аргумент). Сообщение является необязательным, если указан флаг -c . Поскольку наша программа умеет только обрабатывать сообщения и выполнять команды, она отказывается создавать другие типы сигналов.

После заполнения структуры событий передать ее сигнальному API совсем просто:

Листинг 4. Разбудите меня, когда все закончится
cookie = alarm_event_add(&event);
if (cookie == 0) {
        die("got an error: %d", alarmd_get_error());
        exit(1);
}

В целом это все. Мы можем создавать сигналы, и они должны работать. К сожалению, существует несколько нюансов.


Нюансы

Я обнаружил эти нюансы при поверхностном тестировании. Возможно, их больше, но эти четыре заставляют обратить внимание на некоторые моменты использования API C совместно с сигнальным демоном и самого демона.

Нюанс №1: %s alarm

По умолчанию, если заголовок не указан, ему присваивается значение "%s alarm", что, вероятно является ошибкой. Это происходит, если вы оставляете поле event.title пустым или указываете вместо него пустую строку.

Решение: укажите для поля event.title значение "Alarm!" до обработки пользовательских аргументов. Если пользователь не укажет заголовок, будет обеспечено безопасное значение. Это предотвратит досадные сбои.

Нюанс №2: нужно ли отображать диалог?

Существует флаг, отвечающий за отображение диалогового окна события. Поскольку диалоговое окно имеет смысл только при наличии сообщения, при пустом сообщении программа устанавливает флаг "no dialog" (нет диалогового окна). Однако пустое сообщение рассматривается как ошибка, если нет команды для выполнения, так как напоминание, которое не выполняет команд и не отображает сообщений – бесполезно. В общем, этому можно найти ограниченное применение: хитроумный пользователь может воспользоваться возможностью загрузки по расписанию для автоматического пробуждения системы без выполнения каких-либо команд.

Листинг 5. Логика пользовательского интерфейса.
if (!event.exec_name && (argc - optind) < 2) {
        usage("если вы не назначаете команду, вы должны указать сообщение");
}
event.alarm_time = parse_time(argv[optind]);
event.message = parse_message(argv, optind + 1);
if (event.message[0] == '\0') {
        event.flags |= ALARM_EVENT_NO_DIALOG;
}

Нюанс №3: звуковые файлы

Аргумент звукового напоминания должен указывать имя воспроизводимого файла, но у многих ли пользователей есть под рукой множество аудиофайлов? В действительности у всех. Большая коллекция аудиофайлов расположена в каталоге /usr/share/sounds. Однако указание полного пути раздражает пользователей.

Вот мое решение.

Листинг 6. Найди мне звуки!
case 's':
        if (*optarg == '/') {
                event.sound = strdup(optarg);
        } else {
                s = malloc(strlen(optarg) + 23);
                sprintf(s, "/usr/share/sounds/%s", optarg);
                if (access(s, R_OK) == 0) {
                        event.sound = s;
                        break;
                }
                sprintf(s, "/usr/share/sounds/%s.mp3", optarg);
                if (access(s, R_OK) == 0) {
                        event.sound = s;
                        break;
                }
                sprintf(s, "/usr/share/sounds/%s.wav", optarg);
                if (access(s, R_OK) == 0) {
                        event.sound = s;
                        break;
                }
                usage("can't find '%s' in /usr/share/sounds, try absolute path.",
                optarg);
        }
        break;

Данный код выполняет поиск аудиофайлов в каталоге по умолчанию и проверяет наиболее распространенные (и поддерживаемые) расширения MP3 и WAV. Эта процедура не ищет файлы в текущем каталоге, потому что это слишком просто, оставляю это пользователям. Отметьте на полях, что звук проигрывается только если отображается диалоговое окно.

Если вы используете для настройки напоминаний приложение Clock (Часы), у вас, как и у меня, может возникнуть вопрос - как настроить увеличение громкости сигнала. Имею честь доложить: это делается автоматически, сигнал проигрывается вначале тихо, и затем громкость постепенно увеличивается.

Нюанс №4: выполнение команд

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

Во-вторых, вы не можете использовать произвольные команды shell. Параметр exec_name интерфейса API C помещенный в функцию g_shell_parse_argv() , не является полноценной оболочкой shell. В частности, это означает, что отсутствуют расширения параметров и подстановка.

Если вы хотите использовать расширения параметров и подстановку, необходимо явно вызывать shell. Для этого я добавил специальный параметр.

Листинг 7. Выход в shell
case 'C':
        s = malloc(strlen(optarg) + 9);
        sprintf(s, "sh -c '%s'", optarg);
        optarg = s;
case 'c':
        event.exec_name = strdup(optarg);
        break;

Внимательный читатель заметит, что это может привести к ошибке, если добавленный аргумент будет содержать одинарные кавычки. Однако решение этой проблемы является задачей программирования в оболочке shell и не имеет отношения к сигнальному API.


Заключение

Сигнальный API является чрезвычайно гибким и неплохо поддерживается. Часть API сигнального демона, написанная на С, проста в использовании и предоставляет хороший набор функций. Хотя этот упрощенный интерфейс не может справиться со всем, на что способны более крупные приложения, он отличается высокой гибкостью; вам, вероятно, удастся легко приспособить его для ежедневных заданий и напоминаний. Хорошо бы также иметь некоторый способ управления существующими сигнальными событиями.

Некоторые приложения для N810 (и N800) имеют собственное управление сигналами, но это выглядит не лучшим решением. Если вы создаете приложение, которое должно выдавать сигналы, потратьте пять минут на изучение сигнального API и используйте его. Это сбережет ваше время и обеспечит лучшую интеграцию с другими программами, использующими сигналы.

Как и в предыдущих статьях, мне бы хотелось поблагодарить за советы пользователей IRC-канала #maemo.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • developerWorks Premium

    Эксклюзивные инструменты для построения вашего приложения. Узнать больше.

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Мобильные приложения, Linux
ArticleID=367975
ArticleTitle=Linux на борту: Разработка приложений для Nokia N810
publish-date=02032009