Как приручить свой Bluetooth. Часть 2

Проблема «приручения» устройств, которые подключены к последовательному интерфейсу (UART) и взаимодействуют с управляющим процессором посредством AT-команд заключается в том, что для работы нужна программа-терминал.

Многие разработчики, вкусив проблему, бросают все свои силы на создание своей уникальной мега-программы терминала. Причём, в творческом порыве нацеливаются на создание «программы на все случаи жизни». Понятно, что к совершенству можно стремиться, но достичь его нельзя по определению. Перфекционизм — это неразумная трата сил.

Мы не будем заниматься «-измом». Мы пойдем другим путём! Тем более, что в Линуксе, в отличие от Виндовс, принято выкладывать свои наработки, а не зажимать исходные коды и давать рафинированный исполняемый модуль, который поправить на свой лад уже нельзя никак.

Сложность создания программы-терминала заключается в том, что:

Во первых, взаимодействие с устройством происходит посредством одного интерфейса — и чтение из-, и запись в- осуществляется один и тот же порт. Причем, запись в последовательный порт активируется действиями пользователя, а чтение из порта подчиняется законам, по которым живёт исполнительное устройство. Иначе говоря, синхронизация между командой пользователя и ответом устройства на эту команду — чисто говоря — условная.

Когда устройство работает исправно, канал связи устойчивый, то — да, никаких проблем нет! Мы устройству даем команду, оно выполняет её, через какое-то время присылает нам ответ. Всё чётко! Структура нашей программы выглядит следующим образом:

while (1)
{
  command = read_keyboard();
  write_to_device(command);
  answer = read_device();
  print(answer);
}

Это не программа, это условный код.

А теперь представьте, что будет происходить с нашей программой, если устройство по какой-либо причине не ответит? Или наоборот, будет что-нибудь нам периодически посылать.

Понятно, что такая алгоритмическая структура не годится для нашей программы.
Чтобы программа хоть как-то жила при внештатных ситуациях и могла нам реально помогать с проблемой приручения устройства, нам придётся отслеживать два события.

Первое событие — проверка клавиатуру на нажатие клавиш. Если нажатия не было, переходим к проверке второго события — определения, что от устройств что-то пришло. Если устройство «молчит», то снова проверяем клавиатуру. И так по кругу с бешеной скоростью процессора.

Вот условный код этого алгоритма:

while (1)
{
  if (is_key_hit())
  {
    command = read_keyboard();
    write_to_device(command);
  }
  if (is_device())
  {
    answer = read_device();
    print(answer);
  }
}

Здесь проблема даже не в том, что процессор будет молотить в холостую и тратить драгоценные миллиампер-часы аккумулятора. Проблема в том, что нам нужно «насобировать» команду целиком, и только после этого отправлять ее устройству. В противном случае получится так, что мы каждый байт (каждое нажатие на клавишу) будем отправлять в устройство индивидуально.

Есть некоторые устройства, которые воспринимают паузы между набором символов как окончание команды. Иначе говоря, если мы затупим при наборе команды, устройство решит, что команда уже набрана и попытается опознать «недоделанную» нашу команду. Конечно, ничем хорошим это не кончится.

Однозначно такой алгоритм мало что дает для решения наших задач. Если уж быть до конца честны, то на самом деле, такие алгоритмы работали во времена DOS. Программы так и делали: опрашивали клавиатуру, если нажатий не было, то опрашивали устройство. Но вы наверно сами представляете, сложность и одновременно корявость таких программ. Это ж сколько разных нюансов нужно было учесть в работе таких программ! Слава богу, времена DOS давно уже канули в прошлое. По нынешним временам рулят много-поточные программы и многозадачные операционные системы.

Вот и первый камушек в фундамент нашей будущей программы — наша программа должна быть много поточной. Для непосвященных звучит страшно, но на самом деле всё даже проще, чем в DOS-овсих программах. Главное — уловить принцип работы таких программ.

Поскольку между двумя процессами — процессом чтения клавиатуры и отправки команд в устройство, и процессом получения ответов от устройства и распечатки их на экране — мало согласованности, то было бы удобно выделить эти два процесса в отдельные программные единицы, которые бы работали независимо друг от друга.

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

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

Таким образом, наша программ как бы расщепиться на два разных процесса, которые мы будем прописывать в её коде. Причём, код каждого процесса мы будем писать так, как будто второго процесса вовсе не существует. Улавливаете, на сколько упроститься наша задача!

Процесс 1:

while (1)
{
  command = read_keyboard();
  write_to_device(command);
}

Процесс 2:

while (1)
{
  answer = read_device();
  print(answer);
}

И это всё, что нам нужно! Как только будет набрана команда, мы ее отправим в устройство и будем снова набирать следующую команду. Как только будет принят ответ от устройства, мы его тут же напечатаем на экране, и снова перейдем к ожиданию следующей посылки.

С точки зрения архитектуры программы должно быть всё понятно. Вопрос — как запустить эти два независимых процесса проясниться в процессе написания кода программы.

Но прежде чем перейдем к кодированию, я скажу пару слов о графических и текстовых программах.

Текстовые программы иногда называют консольными, но это не совсем правильно. Ведь консоль, по нынешним временам, бывает не только текстовой (страшной, чёрной). Почти все дистрибутивы Линукса обладают красивыми функционально-насыщенными графическими консолями. А к вящему разнообразию еще порой такими, что Виндовс-пользователи внутренне завидуют. Ну, например, несколько рабочих столов только недавно появились в Виндовсе. А православные (истинные) текстовые консоли в Виндовсе отсутствуют как класс, а в Линуксе их аж шесть штук! И это не считая виртуальных консолей, которые физически встроены в ядро! В общем, Винда, отдыхает с её адептами. А мы продолжаем собирать урожай!

Итак, наша программа может быть как текстовой, так и графической. Если программу создавать как графическую, то разнести в разные Text-Box-ы клавиатурные нажатия и прием ответов от устройства будет легко, и они никогда уже не перемешаются друг с другом. В текстовой же консоли, поскольку два процесса выводят информацию в одно место, текстовые строки вполне могут перемешиваться и образовывать «винегрет».

Но написать графическую программу несколько сложнее, чем текстовую. Наша задача состоит в том, чтобы вообще понять принцип построения подобных программ, а не «сделать красиво». Когда вы научитесь писать надёжно работающий функционал, перейти от текста к график — будет пара вечеров!

А сейчас переходим к третьей части Марлезонского балета.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s