FIXME:

Двунаправленный надежный байтовый (октетный) поток является, по-видимому, одной из наиболее распространенных форм обеспечения связи между программами и устройствами. Такой поток реализует, например, последовательный перефирийный интерфейс (англ. SPI), универсальный асинхронный приемопередатчик (англ. UART), протокол управления передачей (англ. TCP) и протокол защищенной оболочки (англ. SSH), etc. Ниже рассмотрены некоторые варианты определения границ сообщений при использовании каналов связи такого рода.

Варианты реализации

В простейшем случае — который, на наш взгляд, наиболее подходит для применения в микроконтроллерный устройствах (в особенности если канал не является, на деле, надежным) — все сообщения имеют длину один байт. Проблема определения границ сообщений, тем самым, решается нижележащим протоколом.

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

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

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

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

Другим вариантом является выбор определенных последовательностей-разделителей, «запрещенных» в потоке данных (и, следовательно, требующих «экранирования» в тех случаях, где их все же необходимо передать.) Такой последовательностью может быть некоторый выбранный байт (как, например, в случае RFC 1055 «SLIP»), или же протокол может опираться на ASCII-кодирование и использовать обычную для Internet последовательность CR LF («конец строки») в качестве признака конца сообщения.

Можно предложить следующие вспомогательные средства обеспечения надежного определения границ сообщения:

  1. ограничение на время ожидания очередного байта сообщения;
  2. переустановление соединения при подозрении на потерю границы сообщения;
  3. включение в концевик или заголовок сообщения контрольной суммы (от простого сложения в коде дополнения до единицы, как используется в протоколах IPv4, TCP, UDP, etc.; до различных вариантов CRC.)

Пример протокола

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

  1. 2 байта — код типа сообщения; может включать сравнительно короткий блок параметров;
  2. 2 байта — длина поля данных N;
  3. N байт — данные;
  4. 2 байта — контрольная сумма (вычисляемая по сообщению в целом.)

В случае, когда не предполагается наличие большого количества команд и (или) полей данных большого размера, разрядности кода типа сообщения и длины поля данных могут быть сокращены до байта. Напротив, увеличить предельную длину поля данных можно или увеличив разрядность поля длины до четырех байт, или потребовав указания длины в 2- или 4-байтных словах.

Для «обычной» задачи считывания данных и управления рядом устройств, набор типов сообщений может быть подобным:

  1. → запрос версии (N = 0);
  2. ← ответ версии (N > 0);
  3. → сброс и установка начальных параметров (N ⩾ 0);
  4. ← ошибка: команда не опознана (N = 0);
  5. ← ошибка: недопустимое значение (N = 0);
  6. ← команда выполнена (N = 0);
  7. → запрос состояния (N = 0);
  8. ← текущее состояние (N > 0);
  9. → установка текущих параметров (N > 0.)

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

С другой стороны, можно использовать и полностью непрозрачный, случайный идентификатор версии; как, например, UUID.

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

Отметим, наконец, что протокол выше имеет некоторое сходство с Modbus, что объясняется сходством решаемых задач.