Как анализировать многострочные последовательные данные с неизвестным количеством строк?

Вот пример данных, которые я прочитал в H-Term:

\r\n
\r\n
NO:0015\r\n    
G:   5.97kg\r\n    
T:   0.00kg\r\n    
N:   5.97kg\r\n
\r\n
\r\n

Как сохранить число, включая «кг», в каждую переменную (NO, G, T, N)?

, 👍0

Обсуждение

так вам нужно сохранить строки?, @Jaromanda X

да, мне нужно сохранить это в 4 переменных, представляющих каждую строку, @El Kazma

что ты пробовал?, @Juraj


1 ответ


2

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

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

Вы сталкиваетесь с двумя основными проблемами:

  1. Моделирование входных данных
  2. Обработать данные

Моделирование входных данных означает осмысленное структурирование ожидаемых входных данных. Глядя на предоставленные входные данные, я вижу этот формат

  • Символы 0–N обозначают некоторые метаданные, разделенные двоеточием (:).
  • Пробелы от 0 до N
  • Цифры от 0 до N с дополнительной десятичной точкой (0015,5,97)
  • Обозначение единицы измерения 0-N (кг)
  • данные разделяются символом CRLF (\r\n)

Теперь представьте себе каждую строку как пакет информации. В этом смысле давайте создадим некоторые структуры данных, описывающие данные пакета.

// Типы
typedef enum {
  NoType,
  NO,
  G,
  T,
  N,
} Type;

// Единицы
typedef enum {
  NoUnit,
  uKg, // килограмм
  uG,  // грамм
} Unit;

// Значения — объединения удобны для моделирования вариантов.
typedef union {
  char str[5];
  float dval;
} Value;

Учитывая вышеизложенное, давайте смоделируем пакет:

// модель данных для пакета с разделителями CRLF
typedef struct {
  Type type;
  Value value;
  Unit unit;
  byte valid;
  // метаданные
  byte expectUnit; // 1, если значение имеет единицу измерения; 0 иначе.

} Packet;

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

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

Вот основная функция обработки, которая принимает строку входных данных (строку) и создает пакет. В общем, я собираюсь предоставить только тип «G», а остальное оставлю вам.

// readPacket обрабатывает входную строку pszBuffer и соответствующим образом заполняет pPacket.
//
// pszBuffer должен быть строкой символов, разделенной \r\n.
// pPacket — это указатель на пакет, который заполняется этой функцией.
//
// возвращает 1, если пакет действителен, и 0 в противном случае.
byte readPacket(Packet* pPacket, const char* pszBuffer) {
  byte isvalid = 0;
  if (pszBuffer && pPacket) {
    char tmp[10];
    char* psz = (char*)pszBuffer;
    char* pszTmp = 0;
    byte len = 0;
    // инициализируем пакет
    pPacket->type = Type::NoType;
    pPacket->unit = Unit::NoUnit;
    pPacket->valid = 0;
    pPacket->expectUnit = 0;
    // получаем тип
    psz = processType(pPacket, psz);
    if (psz) {
      // теперь пропускаем любые пробелы.
      psz = trimLeadingSpaces(psz);
      psz = processValue(pPacket, psz);     
      // если Type обработан, проверяем единицы и завершаем.
      if (psz) {
        if (pPacket->expectUnit)
          pPacket->valid = processUnit(pPacket, psz);
        else
          pPacket->valid = 1;
      }

    } // Тип не найден

    isvalid = pPacket->valid;
  }
  return isvalid;
}

Хорошо, это не так уж и много. Логика такая

  • Получить тип пакета
  • Если тип пакета найден, получите значение пакета
  • Если значение найдено, при необходимости получите единицу измерения

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

// Тип процесса
// Устанавливает pPacket->type в тип, найденный в psz. Тип разделяется двоеточием (:)
// Возвращает 0 (ноль), если известный тип не найден.
// В противном случае PTR для данных после разделителя-двоеточия.
//
char* processType(Packet* pPacket, const char* psz) {
  // находим разделитель двоеточие (:) и возвращаем то, к чему он ведет.
  byte len = 0;

  char* ptr = strchr((char*)psz, ':');
  char* pResult = 0;
  if (ptr) {
    // найдено двоеточие.
    char buffer[4];
    byte len = (ptr - psz) / sizeof(char); // математические вычисления указателя для определения длины.
    strncpy(buffer, psz, len);
    // нулевой термин бафф
    buffer[len] = 0;
    // предполагаем успех; по умолчанию будет оставаться неудачным.
    pResult = (char*)psz + len + 1; // +1, чтобы пропустить символ двоеточия.
    switch (buffer[0]) {
    case 'G':
      pPacket->type = Type::G;
      pPacket->expectUnit = 1; // необходимо указать единицу измерения.
      break;
    default:
      pPacket->type = Type::NoType;
      pResult = 0; // ошибка, не найден известный тип.
    }
  }

  return pResult;
}

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

Если метод ProcessType() обнаружил допустимый тип, следующим шагом будет получение значения на основе этого типа с помощью функцииprocessValue

// Устанавливает значение pPacket-> в соответствии с типом пакета.
// Возвращает ноль (0), если тип не поддерживается или значение не найдено,
// в противном случае указатель на оставшиеся символы после обрабатываемого значения.
char* processValue(Packet* pPacket, const char* psz) {
  char* ptr = 0;
  // значение определяется типом
  switch (pPacket->type) {
  case Type::G:
    ptr = processFloat(pPacket, psz);
    break;
    //
    //добавляем дополнительные реализации типов...
    //
  default:
    // неверно, установите для psz значение null.
    ptr = 0;
  }
  return ptr;
}

Отлично. Теперь давайте посмотрим, как получить значение числа с плавающей запятой:

// Устанавливает pPacket->value.dval
// Возвращает 0 (ноль), если значение не найдено.
// В противном случае ptr после последнего символа десятичного значения.
char* processFloat(Packet* pPacket, const char* psz) {
  const char *float_chars = ".0123456789";
  char* ptr = (char*)psz;
  if (ptr) {
    // сканируем ptr, пока не найдем совпадение.
    byte index = 0;
    while (strchr(float_chars, (char)ptr[index]))
      index++;

    if (index) {
      // получаем число с плавающей запятой, используя временный буфер.
      // опасный! при условии, что это не будет переполнено!
      char buf[10];
      strncpy(buf, ptr, index);
      // нулевой термин бафф
      buf[index] = 0;
      pPacket->value.dval = atof(buf);
      // Продвигаем ptr за десятичные символы для возврата valie.
      ptr += index;
    }
    else ptr = 0; // неудача
  }
  return ptr;
}

Наконец, если все прошло хорошо, код должен искать модуль, если он ожидается (в зависимости от типа)

// Устанавливает pPacket->unit в единицу измерения, указанную в psz.
// Возвращает 0, если единица измерения не найдена, в противном случае — ненулевое значение.
byte processUnit(Packet* pPacket, const char* psz) {
  pPacket->unit =  Unit::NoUnit;
  if (psz) {
    switch ((char)*psz) {
    case 'k':
      pPacket->unit = Unit::uKg;
      break;
    case 'g':
      pPacket->unit = Unit::uG;
      break;
    default:
      break; // модуль инициализирован как NoUnit.
    }
  }
  return pPacket->unit != Unit::NoUnit;
}

Вот GIST скетча Arduino с использованием приведенного выше кода.

Удачи, надеюсь, это поможет. В будущем задавайте вопросы, касающиеся обработки, на StackOverflow.

,