Как мне сделать правильный сканер длинных строк для обнаружения определенных тегов в потоке символов?

Вот мой код:

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // прием, передача

void setup() {
    Serial.begin(9600);
    BTserial.begin(9600);
    Serial.println("Communication with HC-05 successfully started.");
}


bool is_uploading_book = false;
char c = ' ';
String recv_scanner = "";
void loop(){
    if (BTserial.available()) {
        c = BTserial.read();
        recv_scanner += c;
        if(recv_scanner.length() >= 50) {
          recv_scanner.remove(0, 1);
        }
        // Мне кажется, что-то не так с recv_scanner.
        if(is_uploading_book) {
          Serial.write(c);
        } else {
          if(recv_scanner.indexOf("(__BOOK_UPLOAD_START__)") >= 0) {
            is_uploading_book = true;
          }
        }
        Serial.println("\n\n" + recv_scanner + "\n\n");
    }
}

Я отправляю книгу (длинный текст) в виде символов один за другим с телефона на модуль HC-05 (и на Arduino).
Моя цель – определить тег "(_BOOK_UPLOAD_START_)" во входящих символах.
Arduino позволяет мне получать символы книги по одному в каждом цикле.
Я сохраняю эти символы в строке с именем recv_scanner, где я допускаю максимальный размер 50 символов, и как только он достигает этих 50 символов, я начинаю удалять первый символ и добавлять новый на каждый цикл (поэтому он может видеть только 50 символов за раз, потому что максимальная длина строки составляет 200, а для моих целей достаточно 50).
Дело в том, что он работает неправильно.
Когда я Serial.println recv_scanner в каждом цикле (чтобы я видел, как он продвигается), он начинает выглядеть так:

⸮qsnieq⸮cn .laslvanm.⸮⸮⸮d⸮alvuv⸮⸮urosqiam tr⸮⸮⸮

(Все начинается нормально, а затем медленно переходит в это)
Я понятия не имею, что это такое.
Насколько я предполагаю, он должен показывать 50 символов, и они должны быть частью книг.
Есть ли что-то фундаментальное в строках, о чем я не знаю?
Я делаю это, чтобы определить тег (_BOOK_UPLOAD_END_) в конце книги и завершить загрузку.
Не стесняйтесь задавать мои вопросы, если что-то непонятно.

, 👍2


2 ответа


Лучший ответ:

5

Еще один вариант — не сохранять символы в буфере, а вместо этого проверять каждый из них по мере их поступления, чтобы определить, является ли символ следующим в последовательности тегов.

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

const char* TAG_STR="(__BOOK_UPLOAD_START__)";
int tag_pos = 0;
char c;
bool is_uploading_book = false;


void loop() {
  if (BTserial.available()) {
    c = BTserial.read();
    if (is_uploading_book) {
      //делаем книги
    } else {
      if (c == TAG_STR[tag_pos]) {
        // c соответствует следующему символу в строке
        tag_pos++;
      } else if (c == TAG_STR[0]) {
        // совпал первый символ в середине, перезапустить
        tag_pos = 1;
      } else {
        // Не соответствует следующему символу, поэтому мы должны начать сначала
        tag_pos = 0;
      }
      // Следующим совпадающим символом является терминатор строки
      // поэтому мы должны сопоставить всю строку.
      if (TAG_STR[tag_pos] == '\0') {
        is_uploading_book = true;
      }
    }
  }
}
,

Работает отлично!!! Спасибо!, @JingleBells

В этом случае вряд ли возникнет проблема, но алгоритм может дать сбой для таких входных данных, как __BOOK__BOOK_UPLOAD_START__. В tag_pos 7 _ не соответствует U, поэтому алгоритм возвращается к tag pos 1. Но тогда B не соответствует _, и обнаружение не производится. Эта проблема будет возникать каждый раз, когда в строке поиска повторяется первый символ, и ее можно избежать, сравнив строку поиска с самой собой., @jpa

@Крейг Извините за беспокойство. Не могли бы вы взглянуть на это: https://justpaste.it/3wobv, @JingleBells

@jpa да, это предел этого метода., @Craig


1

Я думаю, что ваша проблема заключается в использовании класса String. Каждый раз, когда вы объединяете переменную String, новый буфер для результата выделяется в фоновом режиме посредством динамического выделения памяти. Это может привести к фрагментации кучи , которая довольно быстро съедает вашу память (особенно на небольших компьютерах, таких как Uno).

Вместо этого в качестве кольцевого буфера следует использовать массив char. Давайте сначала определим буфер и инициализируем его нулем:

#define BUFFER_SIZE    50
char buffer[BUFFER_SIZE] = {0};

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

uint8_t buffer_start = 0;
uint8_t buffer_end = 0;

Когда buffer_start == buffer_end, буфер пуст. Когда buffer_end находится на одну позицию раньше buffer_start, буфер заполнен. Чтобы поместить в буфер один символ, делаем следующее (обратите внимание, что c — это та же переменная, что и в вашем коде):

if( ((buffer_end + 1) % BUFFER_SIZE) == buffer_start)
{
    buffer_start = (buffer_start + 1) % BUFFER_SIZE;
}
buffer[buffer_end] = c;
buffer_end = (buffer_end+1) % BUFFER_SIZE;

Что мы здесь делаем? Сначала нам нужно проверить, заполнен ли наш буфер. Мы делаем это, сравнивая buffer_start со следующей позицией вашего buffer_end. Вычисление новой позиции — вот где происходит волшебство. Сначала мы увеличиваем buffer_end на единицу, а затем берем модуль с размером буфера. Модуль - это остаток от целочисленного деления. Когда позиция находится в конце массива buffer, это будет (49 + 1) % 50, что равно нулю. Это переносит конец массива в конец массива для нас.

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

Затем мы сохраняем полученный символ в наш буфер и увеличиваем buffer_end, как описано выше.

Приведенный выше принцип также называется циклическим буфером FIFO (First In First Out), хотя в реальном буфере FIFO отсутствуют некоторые функции, которые здесь вам не нужны.


Теперь, когда у нас есть кольцевой буфер, который заполняется данными, нам также нужно реализовать функцию для поиска строки в буфере, что эквивалентно String.indexOf() метод. Это может выглядеть примерно так:

int find_string(char str[], const char search[]){
    uint8_t start = buffer_start;
    uint8_t search_pos = 0;
    uint8_t result_pos = -1;
    while(start != buffer_end){
        if(str[start] == search[search_pos]){
            if(search_pos == 0) result_pos = start;
            search_pos++;
            if(search[search_pos] == '\0') return result_pos;   
        } else {
            result_pos = -1;
            search_pos = 0;
        }
        start = (start + 1) % BUFFER_SIZE;
    }
    return -1;
}

Обратите внимание, что эта реализация протестирована лишь приблизительно, а не полностью.

,

Здравствуйте! Большое спасибо за подробный ответ. Я изо всех сил пытаюсь понять, что и как добавить в функцию find_string(). если (найти_строку (?, ?)) { ... }, @JingleBells

Не могли бы вы предоставить код для моего конкретного случая, потому что я не уверен, как правильно его реализовать., @JingleBells

Первый параметр — буфер, второй — строка для поиска. Вы можете использовать его так же, как метод String.indexOf()., @chrisl

https://i.ibb.co/bJ3kS9v/img.png Я не думаю, что это должно произойти. Я делаю что-то неправильно?, @JingleBells

Если if(is_uploading_book) { } не имеет проверки find_string, он работает нормально. Дело в том, что мне нужна проверка find_string., @JingleBells

Кажется, что-то еще не так. Вы уверены, что в HC-05 настроена правильная скорость передачи данных?, @chrisl

Я использовал решение Крейга, и оно отлично работает. Моя основная проблема возникла, когда в части if (is_uploading_book) { } я попытался снова проверить второй тег (UPLOAD_BOOK_END). В любом случае, это работает. Большое спасибо за ваше время и внимание. Если вы знаете, что происходит, мне любопытно услышать., @JingleBells

Извините, я честно не знаю, что там произошло. Оба решения не используют String, что хорошо. Хорошо, что теперь у вас есть рабочее решение, @chrisl

Давайте [продолжим это обсуждение в чате](https://chat.stackexchange.com/rooms/105550/discussion-between-novalium-company-and-chrisl)., @JingleBells