Как анализировать многострочные последовательные данные с неизвестным количеством строк?
Вот пример данных, которые я прочитал в 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)?
@El Kazma, 👍0
Обсуждение1 ответ
Это слишком широкий вопрос, который граничит с «напишите мне код» — это не служба кодирования — и если что-то принадлежит StackOverflow, поскольку это касается обработки. При этом этот вопрос и здесь будет восприниматься как слишком широкий.
Я даю ответ, потому что считаю, что он может помочь другим здесь, поскольку касается некоторых фундаментальных концепций программирования. В любом случае это не идеальное решение, например, оно не учитывает размер программы. Скетч смотрите в конце поста.
Вы сталкиваетесь с двумя основными проблемами:
- Моделирование входных данных
- Обработать данные
Моделирование входных данных означает осмысленное структурирование ожидаемых входных данных. Глядя на предоставленные входные данные, я вижу этот формат
- Символы 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.
- String() против char для простого управления потоком
- Arduino Serial.ReadString() проблема
- Создание строк с символами UTF-8 из данных
- Найдите ОК или ОШИБКУ в последовательной строке
- Новичок, изучающий Serial.readString()
- Команда через последовательный монитор не работает должным образом в Arduino
- Очистка последовательных данных для новых входящих значений
- Как разделить входящую строку?
так вам нужно сохранить строки?, @Jaromanda X
да, мне нужно сохранить это в 4 переменных, представляющих каждую строку, @El Kazma
что ты пробовал?, @Juraj