Парсим строки с UART
- Войдите на сайт для отправки комментариев
Вс, 10/09/2017 - 18:59
Всем добра!
История , наверное, покажется кому-нить смешной, но у меня ща башка лопнет. :))
Нужно принимать поток данных и парсить их. С приемом данных все ок, но тут я наткнулся на хню, которая поломала мое представление о мире. Скорее всего из-за недостатка фундаментальных знаний.
Код:
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
Люди добрые, че за фигня такая? Первый раз с таким сталкиваюсь... Буду признателе за конструктивное наставление на путь истинный! ;)
обьясните мне, нафига переменная temp обьявлена double, а выражение (n-k-1), в котором все части вообще байты - как float?
Не говоря уж о том, что для извлечения числа из строки есть куда более простое и менее затратное решение - функция atoi
Ну, я не знал куда уже кидаться, и поэтому явно поприводил аргументы и результат к тому, что хочет функция pow.
Взялся все писать врукопашную, видимо переоценил свои силы. Но ведь код по идее долже работать!
Думаю, это следствие беспорядка с типами переменных в вашей программе. Для проверки попробуйте написать строчку 27 вот так:
result =(unsignedint)temp+0.5;Подозреваю, что после всех Ваших операций со степенями получается что-нибудь типа 350.9999999. println выводит это как 351.00 а приведение к целому округляет вниз. Получается 350.
kalapanga - поддерживаю, коллега :) См. мое сообщение #2
так не работает, а вот так
result =(unsignedint)(temp+0.5);работает! И даже +0.1 достаточно.
Спасибо! Вы оказались правы. Да благословит вас компилятор! Ну или вы его! :)
Можно и я влезу?
Передатчик. Выдает пакеты формата {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.Может где-то происходит выход за границы? Второй день не могу понять причину.
Tomasina - с таким обьемным вопросом лучше новую начать.
Тем более что этот вопрос я уже где-то видел... не дублируйте темы, это читерство :)
Спасибо! Вы оказались правы. Да благословит вас компилятор! Ну или вы его! :)
Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.
Да не я тут развел болото с типами переменных. Неявное приведение работало точно так же. Если уж по феншую, то надо от pow отказываться, на сколько я понимаю.
Ксли уж хочется преобразовывать строку в число самостоятелно, лучше пользоваться более щадящими методаим:
int temp = 0; //пытаемся получить значение for (char k = 0; k < n; k++) { // первое число умножаем на 100, второе на 10, третье на 1 temp = temp*10 + (text[k] - '0');Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.
Но я прислушался к Вашему совету, и вместо pow, которая сожрала у меня столько времени сегодня, написал простую функцию возведения 10 в степень n
unsigned long pow10(unsigned int n){ unsigned long multiplier=1; for (byte i = 0; i<n; i++)multiplier*=10; return multiplier; }Ксли уж хочется преобразовывать строку в число самостоятелно, лучше пользоваться более щадящими методаим:
int temp = 0; //пытаемся получить значение for (char k = 0; k < n; k++) { // первое число умножаем на 100, второе на 10, третье на 1 temp = temp*10 + (text[k] - '0');И действительно - этот вариант куда эллегантнее! :)
А по моему вопросу нет идей?
Tomasina
буфер sbuff никак не защищен от переполнения. И скорее всего. в какой-то момент программа выходит за границы массива
Но ведь в строке 34 есть контроль длины sbuff.
Контроль длины - есть, а защиты от переполнения - нет.
чой-то не понимаю. При превышении длины в 32 символа мы из этой функции вылетаем, если в Serial еще что-то есть, то заново попадаем в эту функцию, а буфер уже пустой, т.к. инициализируется заново и заполняется снова.
Как возникает переполнение?
чой-то не понимаю. При превышении длины в 32 символа мы из этой функции вылетаем
А если длина ровно 32 символа, то не вылетаем, а вместо этого прибавляем к буферу еще один = '\0'
А вот опять не выходит. Буфер теперь не превышает 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 значения и завершает работу.
здесь #76 смотри, как я посимвольно принимаю и собираю в строку. *считай, что у тебя не DigiUSB, а сериал - сути не меняет.
что вижу у тебя дурного - делаи, циклы.
если // посимвольно читаем, то посимвольно читаем, а не хернёй занимаемся.
Томасина, буфер вы опять неверно обрабатываете... не везет вам с буфером... или ему с вами. Но к ошибке это отношения не имеет, так что оставляю это вам как домашнее задание.
Вопрос в другом - с чего вы взялись использовать для этой задачи функцию 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);
if(len > bufferSize - 1 || millis() - t > 1000)// контролируем переполнение буфераО_О
Томасина, последнее замечание (насчет ссылок на переменные) - снимается, не заметил сразу, что у вас все переменные - символьные массивы.
Каких-то явных недоработок в коде я не вижу. Правда, функцию sscanf() знаю довольно поверхностно, а она, как я уже сказал, с сюрпризами.
Ставлю на то, что пакет, на котором происходит сбой - особенный. Для проверки предлагаю добавить вывод в сериал содержимого буфера в HEX-кодах
Ставлю на то, что пакет, на котором происходит сбой - особенный.
ага - проклятый
Не такой уж он и простой. Строку получил, начинаю делить:
Буфер:
{38 11 s T 1400064}\nПосле разбиения получаю указатели на его фрагменты, которые красиво выводятся в Serial:
Но как теперь получить значения, которыми можно пользоваться, а не указатели? Такой код:
возвращает 0 вместо 38.
так
byte sid = atoi(ptrinput);
Пробовал. Тоже 0 получается.
Хм... а со вторым значением нормально получается. Выходит, надо у изначальной строки отрезать первый символ.
Вот так работает, но нет ли утечек памяти?
третью строку запишите так
и тогда весьма уродливая операция в первой строке будет не нужна.
А вот как раз значения state и cmd правильнее копировать из исходного буфера командой strcpy(), а не приравнивать ссылки
Изучал этот вопрос и обнаружил в библиотеке от 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(); }