#1 2025-04-05 09:06:17

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

парсинг NMEA **проблема

Намагаюся розпарсити рядок данних NMEA. Зробив такий скетч.

#include <SoftwareSerial.h>
// Настраиваем пины для подключения GPS-модуля
const int RXPin = 4, TXPin = 3;
SoftwareSerial gpsSerial(RXPin, TXPin);
char nmeaBuffer[88]; // Буфер для строки NMEA
int bufferIndex = 0;
char token_1[88];
char token_2[88]; 
char token_3[88]; 
char token_4[88]; 
char token_5[88]; 
char token_6[88]; 
char token_7[88]; 
char token_8[88]; 
char token_9[88]; 
char token_10[88]; 
char token_11[88]; 

//	$GPRMC,180708.000,A,4829.8325,N,03556.0990,E,4.21,66.09,040425,,,A*5F
//	$GPRMC,180654.000,A,4829.8202,N,03556.0560,E,0.00,,040425,,,A*70
 
void setup() {
  Serial.begin(115200); // Настройка монитора порта
  gpsSerial.begin(9600); // Настройка GPS-модуля
  Serial.println("Ожидание данных...");
}

void loop() {
  while (gpsSerial.available()) {
    char c = gpsSerial.read();
    // Считываем строку, пока не достигнем конца строки
    if (c == 'n') {
      nmeaBuffer[bufferIndex] = ''; // Завершаем строку
      processNMEA(nmeaBuffer); // Обрабатываем строку
      bufferIndex = 0; // Сбрасываем индекс для следующей строки
    } else if (bufferIndex < 87) {
      nmeaBuffer[bufferIndex++] = c; // Сохраняем символ в буфер
    }
  }
}

void processNMEA(char* nmea) {
  // Проверяем, является ли строка строкой $GPRMC
  if (strncmp(nmea, "$GPRMC", 6) == 0) {
    Serial.print("Строка протокола: ");
    Serial.println(nmea); // Вывод всей строки $GPRMC
sscanf(nmea, "%[^','],%[^','],%[^','],%[^','],%[^','],%[^','],%[^','],%[^','],%[^','],%[^','],%[^',']", 
token_1, token_2, token_3, token_4, token_5, token_6, token_7, token_8, token_9, token_10, token_11);
    //Serial.print("Дата: ");
	Serial.print(" token_1 = "); Serial.println(token_1);
	Serial.print(" token_2 = "); Serial.println(token_2);
	Serial.print(" token_3 = "); Serial.println(token_3);
	Serial.print(" token_4 = "); Serial.println(token_4);
	Serial.print(" token_10 = "); Serial.println(token_10);
	Serial.print(" token_11 = "); Serial.println(token_11);
		
    //printFormattedDate(token_10);
  }    }

// Функция для форматирования даты (ДД.ММ.ГГГГ)
void printFormattedDate(char* rawDate) {
  if (strlen(rawDate) < 6) return; // Проверяем длину строки
  char dd[3], mm[3], yy[5];
  strncpy(dd, rawDate, 2); dd[2] = '';
  strncpy(mm, rawDate + 2, 2); mm[2] = '';
  strncpy(yy, rawDate + 4, 2); yy[2] = '';
  Serial.print(dd); Serial.print(".");
  Serial.print(mm); Serial.print(".");
  Serial.print("20"); // Добавляем "20" для полного формата года
  Serial.println(yy);
}

але результат незадовільний

Ожидание данных...
Строка протокола: $GPRMC,055655.062,V,4829.8313,N,03556.0391,E,,,050425,,,N*72

 token_1 = $GPRMC
 token_2 = 055655.062
 token_3 = V
 token_9 = 4829.8313
 token_10 = 
 token_11 = 
Строка протокола: $GPRMC,055656.062,V,4829.8313,N,03556.0391,E,,,050425,,,N*71

 token_1 = $GPRMC
 token_2 = 055656.062
 token_3 = V
 token_9 = 4829.8313
 token_10 = 
 token_11 = 
Строка протокола: $GPRMC,055657.062,V,4829.8313,N,03556.0391,E,,,050425,,,N*70

 token_1 = $GPRMC
 token_2 = 055657.062
 token_3 = V
 token_9 = 4829.8313
 token_10 = 
 token_11 =
Строка протокола: $GPRMC,063155.062,A,4829.8269,N,03556.0546,E,0.30,48.66,050425,,,A*57

 token_1 = $GPRMC
 token_2 = 063155.062
 token_3 = A
 token_9 = 4829.8269
 token_10 = 050425
 token_11 = 
Строка протокола: $GPRMC,063156.062,A,4829.8269,N,03556.0545,E,0.34,41.38,050425,,,A*51

 token_1 = $GPRMC
 token_2 = 063156.062
 token_3 = A
 token_9 = 4829.8269
 token_10 = 050425
 token_11 = 
Строка протокола: $GPRMC,063157.062,A,4829.8270,N,03556.0545,E,0.41,33.89,050425,,,A*55

 token_1 = $GPRMC
 token_2 = 063157.062
 token_3 = A
 token_9 = 4829.8270
 token_10 = 050425
 token_11 = 

Проблема в тому, що серія ком ( ,,, ) функцією sscanf обробляється не корректно ...  sad sad sad

Остання редакція renoshnik (2025-04-05 09:33:22)

Неактивний

#2 2025-04-05 11:03:17

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

renoshnik пише:

Проблема в тому, що серія ком ( ,,, ) функцією sscanf обробляється не корректно ...  sad sad sad

sscanf() - невдалий вибір для парсингу такого синтаксиса. Його тут можна застосувати для парсингу окремого токена, але для рядка в цілому він погано підходить.

Токени розділені комами, так шукайте символ коми. Або за допомогою strchr(), або своєю реалізацією, що не набагато складніше. Потім парсіть вміст між комами.

Навіщо копіювати кожний токен в окремий буфер? Він же і так уже присутній в памʼяті. Якщо для парсинга самого токена він має закінчуватись нульовим символом, заміняйте кому на нульовий символ. 

Якщо не помиляюсь, максимальна довжина рядка NMEA - 82 символи. Тобто буфера довжиною 83 символи буде достатньо. Чому 88?

Взагалі, ви спочатку накопичуєте всю строку в буфер, потім намагаєтесь її парсити. Звісно, можна і так. Але, мені здається, простіше парсити дані по мірі надходження.

Неактивний

#3 2025-04-05 11:22:47

Honey
Учасник
З Київ
Зареєстрований: 2020-09-26
Повідомлень: 443

Re: парсинг NMEA **проблема

Спробуйте без sscanf, якось так:

#include <stdio.h>
#define TOKEN_COUNT 11
#define TOKEN_SIZE  88
char token[TOKEN_COUNT][TOKEN_SIZE];
void process() {
 int i;
 for (i = 0; i < TOKEN_COUNT; i++)
  printf("token[%i]='%s'\n", i, token[i]);
}
int main() {
 int i = 0, j = 0;
 for (;;) {
  char c = getchar();
  if (c == EOF) {
   break;
  } else if (c == '\n') {
   while (i < TOKEN_COUNT) {
    token[i++][j] = 0;
    j = 0;
   }
   i = 0;
   process();
  } else if (c == ',') {
   if (i < TOKEN_COUNT)
    token[i++][j] = 0;
   j = 0;
  } else {
   if (i < TOKEN_COUNT && j < TOKEN_SIZE - 1)
    token[i][j++] = c;
  }
 }
}

Остання редакція Honey (2025-04-05 11:23:29)

Неактивний

#4 2025-04-05 12:17:51

jokeer
Гість

Re: парсинг NMEA **проблема

scanf не вміє парсити порожні значення. Беріть strtok.
Я впевнений, що ця задача вирішена 100500 разів. Я б пошукав готовий парсер wink

#5 2025-04-05 13:00:29

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

Re: парсинг NMEA **проблема

Ось цим подивився що видає блок

#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(4, 3); // RX (к Arduino), TX (к GPS-модулю)
void setup() {
  Serial.begin(115200);
  gpsSerial.begin(9600);
  Serial.println("Начало чтения NMEA-строк...");
}
void loop() {
  if (gpsSerial.available()) {
    static char buffer[128]; 
    static size_t index = 0; 
    char c = gpsSerial.read(); 
    if (c == 'n') { 
      buffer[index] = ''; 
      Serial.println(buffer); 
      index = 0; } 
	else if (index < sizeof(buffer) - 1) {buffer[index++] = c;}
  }
}

отримав такі данні

Начало чтения NMEA-строк...
$GPGGA,085638.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*57
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPRMC,085638.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*72

$GPGGA,085639.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*56
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPGSV,3,1,10,25,82,177,,29,58,268,33,12,46,126,,11,39,054,17*75
$GPGSV,3,2,10,28,37,287,34,20,31,093,23,31,23,312,40,05,17,131,19*79
$GPGSV,3,3,10,06,10,040,,18,09,207,*7E
$GPRMC,085639.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*73

$GPGGA,085640.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*58
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPRMC,085640.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*7D

$GPGGA,085641.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*59
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPRMC,085641.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*7C

$GPGGA,085642.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*5A
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPRMC,085642.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*7F

$GPGGA,085643.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*5B
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPRMC,085643.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*7E

$GPGGA,085644.000,4829.8262,N,03556.0484,E,1,03,7.9,120.1,M,18.9,M,,0000*5C
$GPGSA,A,2,31,29,28,,,,,,,,,,7.9,7.9,1.0*31
$GPGSV,3,1,10,25,82,177,,29,58,268,33,12,46,126,,11,39,054,17*75
$GPGSV,3,2,10,28,37,287,33,20,31,093,22,31,23,312,40,05,17,131,17*71
$GPGSV,3,3,10,06,10,040,,18,09,207,*7E
$GPRMC,085644.000,A,4829.8262,N,03556.0484,E,0.00,,050425,,,A*79

переробив парсер

#include <SoftwareSerial.h>

SoftwareSerial gpsSerial(4, 3); // Пины для подключения GPS (RX, TX)

char value[32];
char buffer[128];
char subBuffer[128]; // Дополнительный буфер для разделения склеенных строк

void parseNMEA(const char* identifier, int commaIndex) {

    while (gpsSerial.available()) {
        memset(buffer, 0, sizeof(buffer)); // Очистка основного буфера
        size_t len = gpsSerial.readBytesUntil('n', buffer, sizeof(buffer) - 1);
        buffer[len] = 'n'; // Завершающий символ строки

        // Проверка на склеенные строки
        if (strstr(buffer, identifier) && strstr(buffer, "$GPGGA")) {
Serial.println("Обнаружена склеенная строка! Разделяем...");
            char* secondPart = strstr(buffer, "$GPGGA"); // Находим начало второй строки
            size_t firstPartLen = secondPart - buffer;   // Вычисляем длину первой строки

            // Копируем первую часть строки в отдельный буфер
            strncpy(subBuffer, buffer, firstPartLen);
            subBuffer[firstPartLen] = 'n';

Serial.print("Первая часть строки: ");
Serial.println(subBuffer);

            // Обрабатываем первую часть строки
            processLine(subBuffer, identifier, commaIndex);

            // Обрабатываем вторую часть строки
Serial.print("Вторая часть строки: ");
Serial.println(secondPart);
            processLine(secondPart, identifier, commaIndex);
            continue;
        }

        // Фильтр: проверяем начало строки
        if (buffer[0] != '$') {
Serial.println("Строка пропущена: некорректный формат");
            continue;
        }

Serial.print("Полученная строка: ");
Serial.println(buffer);

        // Обработка строки с использованием идентификатора
        processLine(buffer, identifier, commaIndex);
    }
}

void processLine(const char* buffer, const char* identifier, int commaIndex) {
    // Проверяем соответствие идентификатору
    if (strncmp(buffer, identifier, strlen(identifier)) == 0) {
        const char* ptr = buffer;
        for (int i = 0; i < commaIndex; i++) {
            ptr = strchr(ptr, ',');
            if (!ptr) {
Serial.println("Ошибка: недостаточно запятых.");
                return;
            }
            ptr++; // Переход к следующему символу после запятой
        }

        // Получаем значение после запятой
        //char value[32];
        size_t i = 0;
        while (*ptr && *ptr != ',' && *ptr != 'r' && *ptr != 'n' && i < sizeof(value) - 1) {
            value[i++] = *ptr++;
        }
        value[i] = 'n';

//Serial.print("Найдено значение: ");
//Serial.println(value);
        return(value);
    }
}

void setup() {
    Serial.begin(115200);
    gpsSerial.begin(9600);
    Serial.println("Инициализация завершена. Ожидаю данные...");
}

void loop() {
    parseNMEA("$GPGSA", 15); // Ищем данные $GPRMC после девятой запятой
Serial.print("-----------Найдено значение  $GPGSA, 15 : ");
Serial.println(value);
    delay(1000); // Увеличенный интервал для предотвращения переполнения
  
    parseNMEA("$GPGGA", 4); // Ищем данные $GPRMC после девятой запятой
Serial.print("-----------Найдено значение  $GPGGA, 4 : ");
Serial.println(value);
    delay(1000); // Увеличенный интервал для предотвращения переполнения
  
  parseNMEA("$GPRMC", 9); // Ищем данные $GPRMC после девятой запятой
Serial.print("-----------Найдено значение  $GPRMC, 9 : ");
Serial.println(value);
    delay(1000); // Увеличенный интервал для предотвращения переполнения

Serial.println("+++++++++++++++++++++++++++++++");
  
}

тепер отримую таке

Инициализация завершена. Ожидаю данные...
Найдено значение  $GPGSA, 15 : 
Обнаружена склеенная строка! Разделяем...
Первая часть строки: 

Вторая часть строки: $GPGGA,095405.000,4829.8325,N,03556.0791,E,1,03,25.2,189.4,M,18$GPGGA,095406.000,4829.8319,N,03556.0755,E,1,03,25.1,187.5,M,18.
,M,,0000*6A
$GPGSA,A,2,26,31,28,,,,,,,,,,25.2,25.1,1.0*3D
$G.9⸮
Строка пропущена: некорректный формат
Полученная строка: $GPGSA,A,2,26,31,28,,,,,,,,,,25.2,25.1,1.0*3D

Полученная строка: $G829.8319,N,0355,E,23.78,275.81,050425,,,A*57

Найдено значение  $GPGGA, 4 : 03556.0791

Полученная строка: $GPGGA,095407.000,4829.8315,N,03556.0725,E,1,03,25.1,185.7,M,18$GPGGA,095408.000,4829.8313,N,03556.0708,E,1,03,25.1,184.3,M,18.
A,2,26,31,3556.0708,E,1,03,25.1,184.3,M,18.9,M,,0000*63
$GPGSA,Y
Строка пропущена: некорректный формат
Полученная строка: $GPGSA,A,2,26,31,28,,,,,,,,,,25.1,25.1,1.0*3E

Полученная строка: $GPRMC,095408.000,A,4829.8313,N,03556.0708,E,21.39,276.20,050425,,,A*54

Найдено значение  $GPRMC, 9 : 050425
791

+++++++++++++++++++++++++++++++
Полученная строка: $GPGGA,095409.000,4829.8307,N,03556.0723,E,1,03,25.1,183.0,M,1818,33,214,,28,31,257,20,31,31,287,26,12,22,139,20*7E

Полученная строка: $GPGSV,3,3,10,26,19,309,26,11,17,048,18*79

Полученная строка: $GPRMC,095409.000,A,4829.8307,N,03556.0723,E,18.97,274.54,050425,,,A*56

Найдено значение  $GPGSA, 15 : 050425
791

Обнаружена склеенная строка! Разделяем...
Первая часть строки: 

Вторая часть строки: $GPGGA,095410.000,4829.8299,N,03556.0744,E,1,03,25.1,181.8,M,18$GPGGA,095411.000,4829.8291,N,03556.0784,E,1,03,25.0,180.7,M,18.
,,,,,25.1,25.0,1.0*3F
$G.9,M,,0000*65
$GPGSA,A,2,26,31,28,,,,,.:
Строка пропущена: некорректный формат
Полученная строка: $GPGSA,A,2,26,31,28,,,,,,,,,,25.1,25.0,1.0*3F

Полученная строка: $G829.8291,N,0354,E,14.19,272.45,050425,,,A*50

Найдено значение  $GPGGA, 4 : 03556.0744

Полученная строка: $GPGGA,095412.000,4829.8293,N,03556.0725,E,6,00,50.0,180.5,M,18$GPGGA,095413.000,4829.8294,N,03556.0665,E,6,00,50.0,180.2,M,18.
,M,18.9,M,,0000*6F
$GPGSA,A,2,,,,,,,,,,,,,50.0,,6,00,50.0,180.2⸮!
Строка пропущена: некорректный формат
Полученная строка: $GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06

Полученная строка: $GPRMC,095413.000,V,4829.8294,N,03556.0665,E,14.19,272.45,050425,,,E*4A

Найдено значение  $GPRMC, 9 : 050425
744

+++++++++++++++++++++++++++++++

наче краще але купа якихось артифактів .....

якщо закоментувати перевірочні виводи в монітор то якась маячня виходить...

Инициализация завершена. Ожидаю данные...
Найдено значение  $GPGSA, 15 : 
Найдено значение  $GPGGA, 4 : 

Найдено значение  $GPRMC, 9 : 

+++++++++++++++++++++++++++++++
Найдено значение  $GPGSA, 15 : 

Найдено значение  $GPGGA, 4 : 

Найдено значение  $GPRMC, 9 : 050425

+++++++++++++++++++++++++++++++
Найдено значение  $GPGSA, 15 : 050425

Найдено значение  $GPGGA, 4 : 
50425

Найдено значение  $GPRMC, 9 : 050425

+++++++++++++++++++++++++++++++
Найдено значение  $GPGSA, 15 : 050425

Найдено значение  $GPGGA, 4 : 
50425

Найдено значение  $GPRMC, 9 : 
50425

+++++++++++++++++++++++++++++++
Найдено значение  $GPGSA, 15 : 28
425

Найдено значение  $GPGGA, 4 : 28
425

Найдено значение  $GPRMC, 9 : 050425

+++++++++++++++++++++++++++++++
Найдено значение  $GPGSA, 15 : 050425

Найдено значение  $GPGGA, 4 : 050425

Найдено значение  $GPRMC, 9 : 050425

+++++++++++++++++++++++++++++++

Неактивний

#6 2025-04-05 13:44:09

jokeer
Гість

Re: парсинг NMEA **проблема

Візьміть готовий csv парсер.
Або візьміть приклад з man strtok і переробіть під себе.

#7 2025-04-05 13:45:16

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

Взагалі, ефективна реалізація парсера залежить від того, чи потрібно парсити якість конкретні типи повідомлень, які токени з них потрібні, в якому форматі вони мають бути для зручності подальшої обробки і т.д.
Це класична задача для реалізації у вигляді конечного автомата.

Неактивний

#8 2025-04-05 13:48:49

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

jokeer пише:

Або візьміть приклад з man strtok і переробіть під себе.

strtok() витягає тільки непусті токени. NMEA може містити пусті.

Неактивний

#9 2025-04-05 14:05:54

jokeer
Гість

Re: парсинг NMEA **проблема

Сорян, дійсно wink
Ну, читайте посимвольно. Знайдете кому - смикайте за парсер. Знайдете кінець рядка - фіналізуйте запис. Лічильник полів потрібен.

#10 2025-04-05 14:27:33

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

Re: парсинг NMEA **проблема

dimich пише:
jokeer пише:

Або візьміть приклад з man strtok і переробіть під себе.

strtok() витягає тільки непусті токени. NMEA може містити пусті.

отож бо і воно....

#include <SoftwareSerial.h>
#include <string.h>
SoftwareSerial gpsSerial(4, 3); 
String nmeaSentence = ""; 
void setup() {
  Serial.begin(115200);
  gpsSerial.begin(9600);
  Serial.println("Начало чтения NMEA-строк...");
}

void loop() {
  while (gpsSerial.available()) {
    char c = gpsSerial.read(); 
    if (c == 'n') { 
      GPS_run(nmeaSentence); 
      nmeaSentence = ""; 
    } else if (c != 'r') { 
      nmeaSentence += c; 
    }
  }
}

void GPS_run(String nmea) {
  if (nmea.startsWith("$GPGSA")) { // Проверка на идентификатор строки
    Serial.println("Обработка строки $GPGSA:");
    Serial.println(nmea); // Вывод исходной строки

    char nmeaCStr[nmea.length() + 1];
    nmea.toCharArray(nmeaCStr, nmea.length() + 1);

    char *token = strtok(nmeaCStr, ",");
    int fieldIndex = 1; 

    for (int i = 1; i <= 18; i++) { // для теста обрабатываем ровно 18 полей
      Serial.print("Поле ");
      Serial.print(i);
      Serial.print(": ");
      Serial.println(token);
      token = strtok(NULL, ","); // Переход к следующему значению
    }
  }
}

отримую таке

Начало чтения NMEA-строк...
Обработка строки $GPGSA:
$GPGSA,A,3,31,16,18,26,29,,,,,,,,3.1,1.9,2.4*3D
Поле 1: $GPGSA
Поле 2: A
Поле 3: 3
Поле 4: 31
Поле 5: 16
Поле 6: 18
Поле 7: 26
Поле 8: 29
Поле 9: 3.1
Поле 10: 1.9
Поле 11: 2.4*3D
Поле 12: 
Поле 13: 
Поле 14: 
Поле 15: 
Поле 16: 
Поле 17: 
Поле 18: 
Обработка строки $GPGSA:
$GPGSA,A,3,31,16,18,26,29,,,,,,,,3.1,1.9,2.4*3D

Неактивний

#11 2025-04-05 15:06:53

jokeer
Гість

Re: парсинг NMEA **проблема

Ставите вказівник на початок.
Шукаєте кому.
Якщо знайшли - міняєте на 0. Парсите отримане поле. Переставляєте вказівник на символ після 0. Повторюєте.
Перевіряєте краєві умови, щоб не вийти за границю масива.
Десь так

#12 2025-04-05 15:12:37

jokeer
Гість

Re: парсинг NMEA **проблема

Пишіть функцію для парсера для x64. Можна скористатись нормальним ide з нормальним дебагером. Можна тестами обмазати. Як працюватиме правильно - перенесете на AVR.

#13 2025-04-05 15:20:33

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

renoshnik пише:

отримую таке

Які поля з яких типів повідомлень вам потрібно витягти?

Неактивний

#14 2025-04-05 16:02:09

jokeR
Учасник
Зареєстрований: 2024-12-12
Повідомлень: 63

Re: парсинг NMEA **проблема

https://docs.arduino.cc/libraries/nmeaparser/
Все вже кимсь написано wink

Неактивний

#15 2025-04-05 16:17:57

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

Re: парсинг NMEA **проблема

jokeR пише:

https://docs.arduino.cc/libraries/nmeaparser/
Все вже кимсь написано wink

Це не наш шлях ...

Неактивний

#16 2025-04-05 16:21:08

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

Re: парсинг NMEA **проблема

Дякую всім, вже вийшло те що хотів  big_smile  big_smile  big_smile

Тепер буду нарощувати код ...


dimich пише:
renoshnik пише:

отримую таке

Які поля з яких типів повідомлень вам потрібно витягти?

В мене GPS модуль і треба витягнути данні для роботи (час, дата, швидкість, координати і т.п.)

Неактивний

#17 2025-04-05 16:29:59

jokeR
Учасник
Зареєстрований: 2024-12-12
Повідомлень: 63

Re: парсинг NMEA **проблема

Це не наш шлях ...

arduino-way це використовувати по максимуму вже написаний код.
Ну, хіба що самому цікаво погратися в С wink

Неактивний

#18 2025-04-05 16:45:50

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,057

Re: парсинг NMEA **проблема

jokeR пише:

Це не наш шлях ...

arduino-way це використовувати по максимуму вже написаний код.
Ну, хіба що самому цікаво погратися в С wink

Якась ардуінофобія ... hmm

Неактивний

#19 2025-04-05 17:42:52

jokeR
Учасник
Зареєстрований: 2024-12-12
Повідомлень: 63

Re: парсинг NMEA **проблема

Якась ардуінофобія ... hmm

Навпаки wink
Якщо використовуєте Arduino Core - дивно не бути послідовними і не використовувати ардуїнівські ліби. Ви ж прямо в регістри UART не пишете? Хіба що вам ліцензійно чистий код потрібен..

Неактивний

#20 2025-04-05 19:45:30

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

Та ось вам іще одна наївна реалізація

class nmea
{
    void (nmea::*context_)(char);

    uint8_t buf_idx_;
    uint8_t tok_idx_;
    uint8_t tok_start_;
    uint8_t checksum_;
    bool ready_;

    char buf_[80];
    uint8_t tokens_[80];

    void update_checksum(uint8_t c)
    {
        checksum_ ^= c;
    }

    void skip()
    {
        context_ = &nmea::ctx_skip;
    }

    void reset()
    {
        context_ = &nmea::ctx_reset;
    }

    void append_buf(char c)
    {
        if (buf_idx_ >= sizeof(buf_)/sizeof(*buf_)) {
            skip();
            return;
        }
        buf_[buf_idx_++] = c;
    }

    void append_tok()
    {
        if (tok_idx_ < sizeof(tokens_)/sizeof(*tokens_)) {
            tokens_[tok_idx_++] = tok_start_;
        }
    }

    void ctx_reset(char c)
    {
        ready_ = false;

        if (c != '$') {
            return;
        }

        context_ = &nmea::ctx_tokens;
        buf_idx_ = 0;
        tok_idx_ = 0;
        tok_start_ = 0;
        checksum_ = 0;
    }

    void ctx_skip(char c)
    {
        if (c == '\n') {
            context_ = &nmea::ctx_reset;
        }
    }

    void ctx_tokens(char c)
    {
        if (c == '*') {
            context_ = &nmea::ctx_checksum;
            append_tok();
            append_buf('\0');
            tok_start_ = buf_idx_;
            return;
        }

        if (c == '\n') {
            reset();
            return;
        }

        update_checksum(c);

        if (c == ',') {
            append_tok();
            append_buf('\0');
            tok_start_ = buf_idx_;
            return;
        }

        append_buf(c);
    }

    void ctx_checksum(char c)
    {
        if (c == '\r') {
            if (buf_idx_ - tok_start_ != 2) {
                skip();
            }
            return;
        }
        if (c == '\n') {
            if (buf_idx_ - tok_start_ != 2) {
                reset();
                return;
            }
            append_buf('\0');
            char *endptr;
            uint8_t checksum = strtoul(buf_ + tok_start_, &endptr, 16);
            if (*endptr == '\0' && checksum == checksum_) {
                ready_ = true;
            }
            reset();
            return;
        }

        append_buf(c);
    }

public:
    nmea() : context_ { &nmea::ctx_reset } {};

    bool parse(char c)
    {
        (this->*context_)(c);
        return ready_;
    }

    const char *token(uint8_t idx) const
    {
        return (idx < tok_idx_) ? (buf_ + tokens_[idx]) : "";
    }
}

Детально на баги не тестив, на даних з оригінального поста наче працює.

Приклад використання:

nmea nmea;

void loop()
{
    for (int c; (c = SoftwareSerial.read()) != -1;) {
        if (!nmea.parse(c)) {
            continue;
        }
        const char *type = nmea.token(0);

        Serial.print(F("Parsed: "));
        Serial.print(type);

        if (strcmp(type, "GPGGA") == 0) {
            Serial.print(F(" time: "));
            Serial.print(nmea.token(1));
            Serial.print(F("; location: "));
            Serial.print(nmea.token(2));
            Serial.print(nmea.token(3));
            Serial.print(nmea.token(4));
            Serial.print(nmea.token(5));
        }

        Serial.println();
    }
}

Оффтоп: баг з бекслешами на цьому форумі колись пофіксять?

Неактивний

#21 2025-04-05 21:02:35

jokeer
Гість

Re: парсинг NMEA **проблема

Кінцевий автомат воно концептуально. але ж букв дофіга wink і ніхто крім автора не розуміє як воно працює wink

#22 2025-04-05 21:15:54

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

jokeer пише:

Кінцевий автомат воно концептуально. але ж букв дофіга wink і ніхто крім автора не розуміє як воно працює wink

Букв там дофіга бо написано для ардуіни на недо-C++. Для документації продакшн коду можна діаграму намалювати. Або взагалі взяти парсер-генератор (yacc чи bison) і не морочити голову.

Неактивний

#23 2025-04-05 21:38:58

jokeer
Гість

Re: парсинг NMEA **проблема

Я програміст не настоящий wink як це взагалі працює?  на вході опис даних на виході код С?

#24 2025-04-05 21:51:30

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

jokeer пише:

як це взагалі працює?  на вході опис даних на виході код С?

Саме так. Але це має сенс для парсинга нерегулярних синтаксисів. Для регулярних, типу як в NEMA, достатньо КА.
Якби в arduino core була підтримка регулярних виразів, весь той КА можна було би описати однією строкою. Хіба що обчислення контрольної суми довелось би реалізовувати окремо.

Остання редакція dimich (2025-04-05 21:53:02)

Неактивний

#25 2025-04-05 22:01:43

dimich
Учасник
Зареєстрований: 2023-12-01
Повідомлень: 377

Re: парсинг NMEA **проблема

Але суть запропонованої мною реалізації в тому, що дані частково парсяться по мірі надходження. UART працює повільно відносно процесора, тому виконання парсинга "розмазуються" в часі.

Якщо потрібно витягати тільки певні токени у певних типах повідомлень, то можна допиляти парсер, щоб він по закінченню просто випльовував готову структуру з float'ами, чи що там ще потрібно.

Неактивний

Швидке повідомлення

Введіть повідомлення і натисніть Надіслати

Підвал форуму