Парсим строки с UART

5N62V
Offline
Зарегистрирован: 25.02.2016

Всем добра!

История , наверное, покажется кому-нить смешной, но у меня ща башка лопнет. :))

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

Код:

byte text[]  = {51, 53, 49, 46, 55}; // соответствует 351.7
unsigned int result = 0;

void setup() {
  Serial.begin(38400);

}

void loop() {
  Serial.println();
  int n = -1;
  do {      // Определяем сколько знаков содержится до точки (код 46)
    n++;
  } while (text[n] != 46);
  Serial.print("n = "); Serial.println(n);

  parse_line(n);

}

void parse_line(int n) {
  double temp = 0;                      //пытаемся получить значение 
  for (char k = 0; k < n; k++) {        // первое число умножаем на 100, второе на 10, третье на 1
    temp += (text[k] - 48) * pow(10., (float)(n - 1 - k));                                              // и накапливаем это в temp
    Serial.println(temp);
  }
  result =(unsigned int)temp;
  Serial.println(result);
}

в массиве записано  число 351.7    , мне достаточно считать 3 , 5 и 1   и перевести это в число 351 и засунуть в переменную.   И все вроде бы работает до момента передачи значения из переменной double temp  в переменную unsigned int result. Вместо 351 я получаю 350... 

n = 3
300.00
350.00
351.00
350
 
Люди добрые, че за фигня такая? Первый раз с таким сталкиваюсь... Буду признателе за конструктивное наставление на путь истинный! ;)
b707
Онлайн
Зарегистрирован: 26.05.2017

обьясните мне, нафига переменная temp обьявлена  double, а выражение  (n-k-1), в котором все части вообще байты - как float?

Не говоря уж о том, что для извлечения числа из строки есть куда более простое и менее затратное решение - функция atoi

5N62V
Offline
Зарегистрирован: 25.02.2016

Ну, я не знал  куда уже кидаться, и поэтому явно поприводил аргументы и результат к тому, что хочет функция pow.

 

Взялся все писать врукопашную, видимо переоценил свои силы. Но ведь код по идее долже работать! 

b707
Онлайн
Зарегистрирован: 26.05.2017

Думаю, это следствие беспорядка с типами переменных в вашей программе. Для проверки попробуйте написать строчку 27 вот так:

result =(unsigned int)temp+0.5;

kalapanga
Онлайн
Зарегистрирован: 23.10.2016

Подозреваю, что после всех Ваших операций со степенями получается что-нибудь типа 350.9999999. println выводит это как 351.00 а приведение к целому округляет вниз. Получается 350.

b707
Онлайн
Зарегистрирован: 26.05.2017

kalapanga - поддерживаю, коллега :) См. мое сообщение #2

5N62V
Offline
Зарегистрирован: 25.02.2016

так не работает, а вот так

result =(unsigned int)(temp+0.5);

работает! И даже +0.1 достаточно. 

Спасибо! Вы оказались правы.   Да благословит вас компилятор! Ну или вы его! :)

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

Можно и я влезу?

Передатчик. Выдает пакеты формата {38 0 s L 3000}

#include <SoftwareSerial.h>
#define RS485_RX_PIN          6
#define RS485_TX_PIN          5
#define RS485_START_BYTE    '{'  // флаг начала пакета
#define RS485_STOP_BYTE     '}'  // флаг конца пакета
#define RS485_DIVIDER       '\t'  // разделитель данных в пакете
SoftwareSerial rs485(RS485_RX_PIN, RS485_TX_PIN);  // receive pin, transmit pin
const byte thisDeviceID = 38;

void setup()
{
  rs485.begin(57600); // software serial for talking to other devices
}

void rs485send(const byte destination, const char command, const unsigned long value)
{
  const char state = 's';
  rs485.write(RS485_START_BYTE);
  rs485.print(thisDeviceID);
  rs485.write(RS485_DIVIDER);
  rs485.print(destination);
  rs485.write(RS485_DIVIDER);
  rs485.print(state);
  rs485.write(RS485_DIVIDER);
  rs485.print(command);
  rs485.write(RS485_DIVIDER);
  rs485.print(value);
  rs485.write(RS485_STOP_BYTE);
  rs485.println();
}

void loop()
{
  static unsigned long rs485LastMillis = millis();
  if (millis() - rs485LastMillis > 1337)
  {
    if (thisDeviceID == 38) rs485send(11, 'T', millis());
    if (thisDeviceID == 44) rs485send(0, 'K', 0);
    rs485LastMillis = millis();
  }

  static unsigned long lastLED = millis();
  if (thisDeviceID == 38)
  {
    if (millis() - lastLED > 12000)
    {
      rs485send(0, 'L', 3000);
      lastLED = millis();
    }
  }
} // loop

С передатчиком проблем нет, все четко и стабильно (отслеживаю с ноутбука с RS485-USB адаптером).

Приемник:

#define RS485_START_BYTE    '{'  // флаг начала пакета
#define RS485_STOP_BYTE     '}'  // флаг конца пакета
#define RS485_DIVIDER       '\t' // разделитель данных в пакете

const byte thisDeviceID = 39;
boolean networkUpdated = false;   // флаг того, что пришли новые данные по сети

struct packetRx        // полученные из сети данные
{
  byte senderID;
  byte destination;
  char senderState;
  char command;
  unsigned long value;    // от 0 до 4294967295
} networkData;           // имя структурной переменной

void setup()
{
  Serial.begin(57600);
  pinMode(LED_BUILTIN, OUTPUT);
}

void serialEvent() // вызывается по прерыванию (наличие данных в аппаратном Serial)
{
  const byte bufferSize = 32;
  const byte packetMinSize = 11; //  минимальная длина пакета
  unsigned int len = 0;
  char sbuff[bufferSize];
  unsigned long t = millis();
  if (Serial.available()) delay(50);
  while (Serial.available())
  {
    sbuff[len++] = Serial.read(); // посимвольно читаем
    if (len > bufferSize || millis() - t > 1000) // переполнение буфера
    {
      Serial.println(F("Exit"));
      networkUpdated = false;
      return; // выходим из функции
    }
  }
  sbuff[len++] = '\0'; // закрываем строку

  ////////////// парсинг строки
  Serial.print(F("\nBuffer: ")); Serial.print(sbuff);
  if (len > packetMinSize && sbuff[0] == RS485_START_BYTE && sbuff[len - 4] == RS485_STOP_BYTE) // валидация пакета: проверяем длину, начало и конец пакета
  {
    char sid[3];
    char did[3];
    char state[2];
    char cmd[2];
    char val[16];
    byte result = sscanf(sbuff, "%*c%[0-9] %[0-9] %[a-z] %[A-Z] %[0-9]%*c", sid, did, state, cmd, val);
      Serial.print(F("sscanf:")); Serial.print(result);
      Serial.print(F(" "));       Serial.print(sid);
      Serial.print(F("\t"));      Serial.print(did);
      Serial.print(F("\t"));      Serial.print(state);
      Serial.print(F("\t"));      Serial.print(cmd);
      Serial.print(F("\t"));      Serial.print(val);
      Serial.println();

    if (result == 5) // все данные успешно распознаны
    {
      // заполняем структуру полученными данными, преобразуя строки в нужный формат
      networkData.senderID = (byte)atoi(sid);
      networkData.destination = (byte)atoi(did);
      networkData.senderState = state[0];
      networkData.command = cmd[0];
      networkData.value = strtoul(val, networkData.value, 10);      // преобразуем строку в число unsigned long
        Serial.print(F("senderID:"));          Serial.print(networkData.senderID);
        Serial.print(F("  destination: "));    Serial.print(networkData.destination);
        Serial.print(F("  senderState: "));    Serial.print(networkData.senderState);
        Serial.print(F("  command: "));        Serial.print(networkData.command);
        Serial.print(F("  value: "));          Serial.print(networkData.value);
        Serial.println();
      networkUpdated = true;
    }
    else
    {
      Serial.println(F("ERROR: Can't parsing string.\n"));
      networkUpdated = false;
    }
    ////////////////////// конец парсинга
  } // конец валидации
  else
  {
    Serial.println(F("\tSkip\n"));
    networkUpdated = false;
  }
} // конец serialEvent()

void loop()
{
  //serialEvent(); // раскомментировать для SoftSerial, а также для плат Micro, Leonardo
  static unsigned long timestamp = 0;
  static unsigned long timeout = 0;
  if (networkUpdated)  // есть новые данные в сети (соответствующие протоколу)
  {
    if (networkData.destination == 0 || networkData.destination == thisDeviceID) // команда для всех или конкретно для этого устройства, остальные игнорируем
    {
      switch (networkData.command)  // анализ команд
      {
        case 'L':
          digitalWrite(LED_BUILTIN, HIGH);
          timestamp = millis();
          timeout = networkData.value;
          break;

        default:;
      }
    }
    networkUpdated = false;
  }

  if (millis() - timestamp > timeout)
  {
    digitalWrite(LED_BUILTIN, LOW);
  }

} // loop

Пакеты принимаются в том же виде, неподходящие отсеиваются.

А вот с разбором принятого пакета странности: первые минуту-две работает все корректно, а потом парсинг перестает работать.

Вот лог где это видно (строка 14): sscanf возвращает уже 4 обработанных элемента вместо 5.

Buffer: {38	11	s	T	2665462}
sscanf:5 38	11	s	T	2665462
senderID:38  destination: 11  senderState: s  command: T  value: 2665462

Buffer: {38	11	s	T	2666804}
sscanf:5 38	11	s	T	2666804
senderID:38  destination: 11  senderState: s  command: T  value: 2666804

Buffer: {44	0	s	K	0}
sscanf:5 44	0	s	K	0
senderID:44  destination: 0  senderState: s  command: K  value: 0

Buffer: {38	11	s	T	2668146}
sscanf:4 38	11	s	T	0
ERROR: Can't parsing string.

Buffer: {38	11	s	T	2669488}
sscanf:4 38	11	s	T	0
ERROR: Can't parsing string.

Может где-то происходит выход за границы? Второй день не могу понять причину.

b707
Онлайн
Зарегистрирован: 26.05.2017

Tomasina - с таким обьемным вопросом лучше новую начать.

Тем более что этот вопрос я уже где-то видел... не дублируйте темы, это читерство :)

b707
Онлайн
Зарегистрирован: 26.05.2017

5N62V пишет:

Спасибо! Вы оказались правы.   Да благословит вас компилятор! Ну или вы его! :)

Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.

5N62V
Offline
Зарегистрирован: 25.02.2016

Да не я тут развел болото с типами переменных.  Неявное приведение работало точно так же. Если уж по феншую, то надо от pow отказываться, на сколько я понимаю.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

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

 int temp = 0;                      //пытаемся получить значение
  for (char k = 0; k < n; k++) {        // первое число умножаем на 100, второе на 10, третье на 1
    temp = temp*10 + (text[k] - '0'); 

 

5N62V
Offline
Зарегистрирован: 25.02.2016

b707 пишет:

Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.

Но  я прислушался к Вашему совету, и вместо pow, которая сожрала у меня столько времени сегодня, написал простую функцию возведения 10 в степень n

unsigned long pow10(unsigned int n){
   unsigned long multiplier=1;
      for (byte i = 0; i<n; i++)multiplier*=10;
   return multiplier;
}

 

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:



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

 int temp = 0;                      //пытаемся получить значение
  for (char k = 0; k < n; k++) {        // первое число умножаем на 100, второе на 10, третье на 1
    temp = temp*10 + (text[k] - '0'); 

 

 

И действительно - этот вариант куда эллегантнее! :)

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

А по моему вопросу нет идей?

b707
Онлайн
Зарегистрирован: 26.05.2017

Tomasina

буфер sbuff никак не защищен от переполнения. И скорее всего. в какой-то момент программа выходит за границы массива

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

Но ведь в строке 34 есть контроль длины sbuff.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Контроль длины - есть, а защиты от переполнения - нет.

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

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

Как возникает переполнение?

 

b707
Онлайн
Зарегистрирован: 26.05.2017

Tomasina пишет:

чой-то не понимаю. При превышении длины в 32 символа мы из этой функции вылетаем

А если длина ровно 32 символа, то не вылетаем, а вместо этого прибавляем к буферу еще один = '\0'

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

А вот опять не выходит. Буфер теперь не превышает 31, но парсинг снова срывается.

Изменения:

void serialEvent() // вызывается по событию - наличию данных в аппаратном Serial
{
  const byte bufferSize = 32;
  const byte packetMinSize = 11; //  минимальная длина пакета
  unsigned int len = 0;
  char sbuff[bufferSize] = "";
  unsigned long t = millis();
  delay(50);
  while (Serial.available())
  {
    sbuff[len++] = Serial.read(); // посимвольно читаем
    if (len > bufferSize - 1 || millis() - t > 1000) // контролируем переполнение буфера
    {
      Serial.print(F("\tBuffer overflow: ")); Serial.print(len); Serial.println(F(". Exit"));
      networkUpdated = false;
      return; // выходим из функции
    }
    sbuff[len] = '\0';
  }

  ////////////// парсинг строки
  Serial.print(F("\nLen: ")); Serial.print(len);
  Serial.print(F("\nBuffer: ")); Serial.print(sbuff);
  if (sbuff[0] == RS485_START_BYTE) // валидация: проверяем начало пакета
  {
    char sid[3] = "";
    char did[3] = "";
    char state[2] = "";
    char cmd[2] = "";
    char val[16] = "";
    byte result = sscanf(sbuff, "%*c%[0-9] %[0-9] %[a-z] %[A-Z] %[0-9]%*c", sid, did, state, cmd, val);
    Serial.print(F("sscanf:")); Serial.print(result); // для отладки
    Serial.print(F(" "));       Serial.print(sid);
    Serial.print(F("\t"));      Serial.print(did);
    Serial.print(F("\t"));      Serial.print(state);
    Serial.print(F("\t"));      Serial.print(cmd);
    Serial.print(F("\t"));      Serial.print(val);
    Serial.println();
    
    if (result == 5) // все данные успешно распознаны
    {
      // заполняем структуру полученными данными, преобразуя строки в нужный формат
      networkData.senderID = (byte)atoi(sid);
      networkData.destination = (byte)atoi(did);
      networkData.senderState = state[0];
      networkData.command = cmd[0];
      networkData.value = strtoul(val, networkData.value, 10);      // преобразуем строку в число unsigned long
      Serial.print(F("senderID:"));          Serial.print(networkData.senderID);   // для отладки
      Serial.print(F("  destination: "));    Serial.print(networkData.destination);
      Serial.print(F("  senderState: "));    Serial.print(networkData.senderState);
      Serial.print(F("  command: "));        Serial.print(networkData.command);
      Serial.print(F("  value: "));          Serial.print(networkData.value);
      Serial.println();
      networkUpdated = true;
    }
    else
    {
      Serial.println(F("\tERROR: Can't parsing string.\n"));
      networkUpdated = false;
    }
    ////////////////////// конец парсинга
  } // конец валидации
  else
  {
    Serial.println(F("\tSkip parsing. Packet is not valid.\n"));
    networkUpdated = false;
  }
} // конец serialEvent()

Вот лог переходного момента:

Len: 21
Buffer: {38	11	s	T	2186862}
sscanf:5 38	11	s	T	2186862
senderID:38  destination: 11  senderState: s  command: T  value: 2186862

Len: 21
Buffer: {38	11	s	T	2188204}
sscanf:3 38	11	s		
	ERROR: Can't parsing string.

Дальше sscanf всегда обрабатывает только 3 значения и завершает работу.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

здесь #76 смотри, как я посимвольно принимаю и собираю в строку. *считай, что у тебя не DigiUSB, а сериал - сути не меняет.

что вижу у тебя дурного - делаи, циклы.

если // посимвольно читаем, то посимвольно читаем, а не хернёй занимаемся.

b707
Онлайн
Зарегистрирован: 26.05.2017

Томасина, буфер вы опять неверно обрабатываете... не везет вам с буфером... или ему с вами. Но к ошибке это отношения не имеет, так что оставляю это вам как домашнее задание.

Вопрос в другом - с чего вы взялись использовать для этой задачи функцию sscanf()? Почему не пошли простым путем типа strtok()

Это очень непростая функция. С одного из англоязычных форумов - "sscanf() sscanf()is as simple for a beginner as diving at 1000 meters deep for someone who can't swim" - " функция sscanf() примерно настолько же "легка" для новичка, как погружение на 1000 метров для того, кто не умеет плавать".

В частности, sscanf() вообще-то ожидает в качестве параметров адреса переменных, а не сами переменные - как то так:

sscanf(line,"(%d,%d,%d,%c)",&x,&y,&level,&type);

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

if (len > bufferSize - 1 || millis() - t > 1000) // контролируем переполнение буфера

О_О

b707
Онлайн
Зарегистрирован: 26.05.2017

Томасина, последнее замечание (насчет ссылок на переменные) - снимается, не заметил сразу, что у вас все переменные - символьные массивы.

Каких-то явных недоработок в коде я не вижу. Правда, функцию sscanf() знаю довольно поверхностно, а она, как я уже сказал, с сюрпризами.

Ставлю на то, что пакет, на котором происходит сбой - особенный. Для проверки предлагаю добавить вывод в сериал содержимого буфера в HEX-кодах

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

b707 пишет:

Ставлю на то, что пакет, на котором происходит сбой - особенный.

ага - проклятый

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

b707 пишет:
Почему не пошли простым путем типа strtok()

Не такой уж он и простой. Строку получил, начинаю делить:

  char *ptrinput;
  ptrinput = strtok (sbuff, RS485_DIVIDER); Serial.println(ptrinput);   // Выделение первой части
  ptrinput = strtok (NULL, RS485_DIVIDER); Serial.println(ptrinput);    // Выделение последующих
  ptrinput = strtok (NULL, RS485_DIVIDER); Serial.println(ptrinput);
  ptrinput = strtok (NULL, RS485_DIVIDER); Serial.println(ptrinput);
  ptrinput = strtok (NULL, RS485_STOP_BYTE); Serial.println(ptrinput);  // Выделение последней части

Буфер:

{38 11 s T 1400064}\n

После разбиения получаю указатели на его фрагменты, которые красиво выводятся в Serial:

38
11
s
T
1400064

Но как теперь получить значения, которыми можно пользоваться, а не указатели? Такой код:

char *ptrinput; ptrinput = strtok (sbuff, RS485_DIVIDER); 
byte sid = atoi(*ptrinput); Serial.println(sid);

возвращает 0 вместо 38.

b707
Онлайн
Зарегистрирован: 26.05.2017

так

byte sid = atoi(ptrinput);

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

Пробовал. Тоже 0 получается.

Хм... а со вторым значением нормально получается. Выходит, надо у изначальной строки отрезать первый символ.

Вот так работает, но нет ли утечек памяти?

  strcpy(sbuff, &sbuff[1]);
  char *ptrinput;
  ptrinput = strtok (sbuff, RS485_DIVIDER);   byte sid = atoi(ptrinput); Serial.println(sid);
  ptrinput = strtok (NULL,  RS485_DIVIDER);   byte did = atoi(ptrinput); Serial.println(did);
  ptrinput = strtok (NULL,  RS485_DIVIDER);   char state = ptrinput[0];  Serial.println(state);
  ptrinput = strtok (NULL,  RS485_DIVIDER);   char cmd = ptrinput[0];    Serial.println(cmd);
  ptrinput = strtok (NULL,  RS485_STOP_BYTE); unsigned long val = strtoul(ptrinput, val, 10); Serial.println(ptrinput);
b707
Онлайн
Зарегистрирован: 26.05.2017

третью строку запишите так

ptrinput = strtok (sbuff+1, RS485_DIVIDER);   byte sid = atoi(ptrinput); Serial.println(sid);

и тогда весьма уродливая операция в первой строке будет не нужна.

А вот как раз значения state и cmd правильнее копировать из исходного буфера командой strcpy(), а не приравнивать ссылки

marth
Offline
Зарегистрирован: 24.09.2019

Изучал этот вопрос и обнаружил в библиотеке от  Megunolink (доступна из установки менеджера пакетов) отличный парсер, и весь набор для работы с внешними командами. Из встроенного примера, HandleSerialCommands, все становится понятно. Надо только обратить внимание, в конце принимаемых команд, должен быть символ перевода каретки.

p.s.

Здесь есть документация по изменению числа команд, буфера, аргументов.

#include "CommandHandler.h" // The serial command handler is defined in here. 

// Most Arduino boards have an LED connected to one of the digital output
// pins. For the Uno, this is pin 13. 
const int LEDPin = 13;

// The number of turtles counted to date. 
int NumberOfTurtles = 0;

// The serial command handler. Receives serial data and dispatches 
// recognised commands to functions registered during setup. 
CommandHandler<> SerialCommandHandler;

void PrintTurtleCount()
{
  Serial.print(F("Number of turtles = "));
  Serial.println(NumberOfTurtles);
}

// -----------------------------------------------------------------------
// Command handlers. 
// These functions are called when a serial command is received. 
void Cmd_GetTurtleCount(CommandParameter &Parameters)
{
  PrintTurtleCount();
}

void Cmd_SetTurtleCount(CommandParameter &Parameters)
{
  NumberOfTurtles = Parameters.NextParameterAsInteger();
}

void Cmd_AddTurtle(CommandParameter &Parameters)
{
  NumberOfTurtles = NumberOfTurtles + 1;
}

void Cmd_LED(CommandParameter &Parameters)
{
  const char *State = Parameters.NextParameter();

  if (strcmp(State, "on") == 0)
  {
    digitalWrite(LEDPin, HIGH);
  }
  else
  {
    digitalWrite(LEDPin, LOW);
  }
}

void Cmd_Unknown()
{
  Serial.println(F("I don't know that command. Try another. "));
}

// -----------------------------------------------------------------------
void setup()
{
  Serial.begin(9600);
  Serial.println(F("MegunoLink Pro Turtle Monitor"));
  Serial.println(F("-----------------------------"));
  PrintTurtleCount();

  Serial.println(F("Supported commands: "));
  Serial.println(F("!SetTurtles n\r\n"));
  Serial.println(F("  Sets the current turtle count to n(an integer)"));
  Serial.println();
  Serial.println(F("!GetTurtles\r\n"));
  Serial.println(F("  Prints the current turtle count"));
  Serial.println();
  Serial.println(F("!AddTurtle\r\n"));
  Serial.println(F("  Adds one to the current turtle count"));
  Serial.println();
  Serial.println(F("!LED on\r\n"));
  Serial.println(F("  Turns an led on"));
  Serial.println();
  Serial.println(F("!LED off\r\n"));
  Serial.println(F("  Turns an led off."));

  // Setup the commands the handler will respond to. The first parameter
  // is the command name, the second is the function that will be called
  // when the command is received. Note that each command name is 
  // inside F(""). This places the command text in program memory to
  // save RAM. 
  SerialCommandHandler.AddCommand(F("SetTurtles"), Cmd_SetTurtleCount);
  SerialCommandHandler.AddCommand(F("GetTurtles"), Cmd_GetTurtleCount);
  SerialCommandHandler.AddCommand(F("AddTurtle"), Cmd_AddTurtle);
  SerialCommandHandler.AddCommand(F("LED"), Cmd_LED);

  SerialCommandHandler.SetDefaultHandler(Cmd_Unknown);

  pinMode(LEDPin, OUTPUT);
}

void loop()
{
  // Call the serial command handler's process function. It will receive
  // the serial data and call the registered function when a 
  // recognized command is received. 
  SerialCommandHandler.Process();
}