Хочу парсить строки

Тема в разделе "Arduino & Shields", создана пользователем Vetrinus, 8 ноя 2015.

  1. Vetrinus

    Vetrinus Гик

    И снова здравствуйте!
    Я, как следует из названия топика, хочу парсить строки. Гугл выдал варианты типа этого и этого.
    Затык в том, что у меня ничего не получилось :D
    Первый не скомпилировался вообще. Удаление кое чего привело код вот к такому виду:
    Код (C++):
    String s = "";

    void setup() {
      Serial.begin(9600);
    }

    void loop() {
    while (Serial.available()) {
      s += Serial.read();
      }
      if (Serial.read() == '\n') {
        Serial.print(s);
        Serial.print('\n');
      }
    }
    Но контроллер бессловесно принимал строки, и ничего не выдавал в ответ. Попробовал также с массивом переменных типа char. Тоже ничего не вышло. Второй вариант с теми же косяками.
    Возник вопрос - что значат символы ' ', и также, надо ли для того, чтобы плата поняла, что строка закончена писать \n? Или надо писать '\n'? Или вообще не надо? Кто-нибудь делал это вообще? А то цели-то у меня наполеоновские, но я даже строку обратно получить не могу.
    Заранее спасибо за ответы.
     
  2. geher

    geher Гуру

    Что используется для обмена с контроллером?
    Если монитор последовательного порта из Arduino IDE, то все похоже на ситуацию, когда в этом самом мониторе выбран режим без посылки символа перевода строки (только возврат каретки или вообще без завершающих строку символов).
     
    Vetrinus нравится это.
  3. Vetrinus

    Vetrinus Гик

    Благодарствую ) В мониторе порта следовало выбрать режим "Новая строка".

    Так, тогда далее. Как понял, в c++ для парсинга строк используют операторы strtok и atoi, верно?
    И если строка будет типовой, то после прогона ее через strtok нужные мне данные всегда будут на месте одного и того же токена, верно? Кстати, а можно ли словами управлять? Я просто вкурсе, что в js есть оператор ifstrcontain, который проверяет в строке наличие чего-либо. Либо мне нужно делать именно через цифры? Например, на месте третьего токена - id устройства, на месте четвертого, допустим, режим работы, и так далее?
     
  4. Vetrinus

    Vetrinus Гик

    Так, попытка использования strtok для строки, хранящейся в классе String оказалась проваленной:
    Код (C++):
    String_parsing:22: error: cannot convert 'String' to 'char*' for argument '1' to 'char* strtok(char*, const char*)'
    cannot convert 'String' to 'char*' for argument '1' to 'char* strtok(char*, const char*)'
    Значит функция работает только с массивом символов char[]. Или нет, и можно что-то сделать?
     
  5. geher

    geher Гуру

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

    strtok работает только с char* или char[].
    Можно пойти двумя дорогами.
    Допустим имеем экземпляр строки, объявленный так:
    String S;
    1. Можно преобразовать String в массив char
    Код (C):

    char tmpstr[256];
    strcpy(tmpstr,S.c_str());
     
    И дальше работать с массивом.
    2. Можно воспользоваться возможностями класса String для разбора строки.
    Например, поиском подстроки или символа
    Код (C):

    int i=S.indexOf(' '); //полагаем, что разделитель - это пробел, его и ищем
     
    Можно выделить подстроки по разделителю
    Код (C):

    String s1=S.substring(0,S.indexOf(' ')); // часть строки до первого разделителя.
    S=S.substring(S.indexOf(' ')+1); // отбрасываем выделенную в другую переменную часть из основной строки
    И там есть еще много функций для анализа строки.
     
  6. Megakoteyka

    Megakoteyka Оракул Модератор

    В таком виде у вас программа рухнет, как только память закончится. Вы постоянно что-то прибавляете к строке, но никогда не обнуляете ее.
     
    DrProg нравится это.
  7. Vetrinus

    Vetrinus Гик

    Господа, где можно более подробную документацию найти по классу String?
    • [*]String()
      [*]charAt()
      [*]compareTo()
      [*]concat()
      [*]endsWith()
      [*]equals()
      [*]equalsIgnoreCase()
      [*]getBytes()
      [*]indexOf()
      [*]lastIndexOf()
      [*]length()
      [*]replace()
      [*]setCharAt()
      [*]startsWith()
      [*]substring()
      [*]toCharArray()
      [*]toLowerCase()
      [*]toUpperCase()
      [*]trim()
     
  8. Vetrinus

    Vetrinus Гик

    В частности интересует оператор equals().
    msdn говорит, что вроде норм штука, но как его использовать, я что-то не понимаю
     
  9. Vetrinus

    Vetrinus Гик

    Почему-то не компилируется, что ему не нравится?
    Код (C++):

    #define led 13
    String input_string = "";
    const String Led_off = "switch led off";
    const String Led_on = "switch led on";
    byte parse;

    void setup() {
      Serial.begin(9600);
    }

    void loop() {
      while (Serial.available()) {
        input_string += Serial.read();
        //input_string=input_string+(Serial.read());
      }
      if (Serial.read() == '\n') {
        Serial.print(input_string);
        Serial.print('\n');
      }
      switch (parse) {
        case 10:
          digitalWrite(led, LOW);
          break;
        case 11:
          digitalWrite(led, HIGH);
          break;
        case 9:
          Serial.println("invalid String");
      }


      byte parse(String input_string, const String Led_off, const String Led_on) {
        if (input_string.compareTo(Led_off) == 0) {
          return 10;
        }
        else if (input_string.compareTo(Led_on) == 0 {
        return 11;
      }
      else return 9;
      }
    }

     
    Код (C++):
    sketch_nov11a.ino: In function 'void loop()':
    sketch_nov11a:34: error: return-statement with a value, in function returning 'void' [-fpermissive]
    sketch_nov11a:36: error: expected ')' before '{' token
    return-statement with a value, in function returning 'void' [-fpermissive]
     
    UPD. Вскрылось столько косяков, ужас. Сейчас рихтую.
     
    Последнее редактирование: 11 ноя 2015
  10. geher

    geher Гуру

    Не вполне понятно, что должно было получиться, но рискну сделать предположения.
    1. Судя по расположению фигурных скобок функция parse объявлена внутри loop, что не есть правильно с точки зрения синтаксиса языков С и С++.
    2. Данная функция ко всему еще и объявлена неправильно. Точка с запятой после скобки, замыкающей список параметров, совершенно ни к чему в этом контексте. Вместо нее должна быть открывающая фигурная скобка.
    Объявление функции должно иметь вид:

    возвращаемый_тип имя_функции(параметры)
    {
    }

    В данном контексте (не вдаваясь в подробности, могут быть и другие ошибки)
    Код (C):

    #define led 13
    String input_string = "";
    const String Led_off ="switch led off";
    const String Led_on ="switch led on";
    byte parse;

    void setup() {
       Serial.begin(9600);
    }

    void loop() {
       while (Serial.available()) {
          input_string += Serial.read();
          //input_string=input_string+(Serial.read());
       }
       if (Serial.read() == '\n') {
           Serial.print(input_string);
           Serial.print('\n');
       }
       switch (parse) {
           case 10:
               digitalWrite(led, LOW);
               break;
           case 11:
               digitalWrite(led, HIGH);
               break;
           case 9:
               Serial.println("invalid String");
       }
    }

    byte parse(String input_string, const String Led_off, const String Led_on) {
        if (input_string.compareTo(Led_off) == 0) {
           return 10;
        }
        else if (input_string.compareTo(Led_on) == 0 {
             return 11;
        }
        else return 9;
    }
     
    И лучше оформлять вложенные синтаксические конструкции отступами. Проще будет разбираться в коде. Чай не на фортране или не на коболе на перфокартах пробивается. :)

    По классу String есть справка на русском языке:
    http://arduino.ua/ru/prog/StringObject
    Только ссылки на примеры ведут на англоязычный сайт.
     
  11. Vetrinus

    Vetrinus Гик

    Прошу прощения, что картинкой, но так немного проще и быстрее. Вот как сейчас все выглядит у меня:
    upload_2015-11-11_22-46-49.png
     
  12. Vetrinus

    Vetrinus Гик

    А вообще странно, что никаких примеров в сети нет. Запилю статейку обязательно, когда все заработает.
     
  13. Megakoteyka

    Megakoteyka Оракул Модератор

    switch(parse) - в данном случае parse содержит в себе адрес функции, тут даже вызов не производится.
    Нужно так:
    Код (C++):
    byte val = parse(...);
    switch(val)
    {
      ...
    }
    И не забудьте передать в parse параметры.
     
    Vetrinus нравится это.
  14. geher

    geher Гуру

    Что-то я торможу. Пропустил много всего.
    1. Естественно, вызов функции parse в switch должен получить параметры.
    2. В наличии два объекта с одним именем parse (переменная и функция)
    3. До кучи потенциальная проблема с приемом строки и определения ее завершения. По хорошему там надо обрабатывать еще несколько моментов, но это уже дополнительные тонкости. Также возможны некоторые моменты, связанные, например, с особенностями настройки монитора последовательного порта.

    Итого примерно так (опять же, теоретически, без проверки, может еще чего пропустил).

    Код (C):

    #define led 13
    String input_string = "";
    const String Led_off ="switch led off";
    const String Led_on ="switch led on";

    void setup() {
       Serial.begin(9600);
    }

    void loop() {
       input_string="";
       while (Serial.available()) {
          char c=Serial.read();
          if (Serial.read() == '\n') { //считается, что команда завершена, если пришел символ перевода строки
             break;
          } else {
             input_string += c;
          }
       }
       if (input_string.length()>0) {
           Serial.println(input_string);
           switch ( parse(input_string, Led_off, Led_on) ) {
              case 10:
                  digitalWrite(led, LOW);
                  break;
              case 11:
                  digitalWrite(led, HIGH);
                  break;
              case 9:
                  Serial.println("invalid String");
           }
        }
    }

    byte parse(String input_string, const String Led_off, const String Led_on) {
        if (input_string.compareTo(Led_off) == 0) {
           return 10;
        }
        else if (input_string.compareTo(Led_on) == 0 {
           return 11;
       }
       else return 9;
    }
     
     
    Vetrinus нравится это.
  15. Vetrinus

    Vetrinus Гик

    А я заметил, что вы добавили в начале каждого loop обнуление строки. Потому что сделал его в конце сам)))
    Вообще - огромное спасибо за помощь. Сейчас уже хоть как-то работает. Другое дело, что неправильно))
    Сейчас ком выдает вот такое нечто:
    upload_2015-11-11_23-34-47.png
    Т.е. где-то код зациклился.

    так, в последней вариации вашего кода, ком, при вводе строки switch led on выдал вот такой результат:
    upload_2015-11-11_23-37-48.png

    Сейчас попробую разобраться, что к чему
     
  16. Vetrinus

    Vetrinus Гик

    Добавил break; в case 9. теперь код выглядит так:
    Код (C++):
    #define led 13
    String input_string = "";
    const String Led_off ="switch led off";
    const String Led_on ="switch led on";

    void setup() {
       Serial.begin(9600);
    }

    void loop() {
       input_string="";
       while (Serial.available()) {
          char c=Serial.read();
          if (c == '\n') { //считается, что команда завершена, если пришел символ перевода строки
             break;
          } else {
             input_string += c;
          }
       }
       if (input_string.length()>0) {
           Serial.println(input_string);
           switch ( parse(input_string, Led_off, Led_on) ) {
              case 10:
                  digitalWrite(led, LOW);
                  break;
              case 11:
                  digitalWrite(led, HIGH);
                  break;
              case 9:
                  Serial.println("invalid String");
                  break;
           }
        }
    }

    byte parse(String input_string, const String Led_off, const String Led_on) {
        if (input_string.compareTo(Led_off) == 0) {
           return 10;
        }
        else if (input_string.compareTo(Led_on) == 0) {
           return 11;
       }
       else return 9;
    }
     
    Ком выдает вот что:
    Код (C++):
    s
    invalid String
    w
    invalid String
    i
    invalid String
    t
    invalid String
    ch led o
    invalid String
    n
    invalid String
     
     
  17. Vetrinus

    Vetrinus Гик

    Впечатление такое, будто бы что-то не так со скоростью передачи данных.
    Увеличил скорость до 115200 бит/с.
    Результат ввода строки такой:
    Код (C++):
    s
    invalid String
    witch l
    invalid String
    ed on
    invalid String
     
     
  18. Vetrinus

    Vetrinus Гик

    Итак, что мы имеем: Отправлена строка "switch led on". Символ переноса строки напечатан автоматическси. В COM выдалась только первая буква. Смотрим - где та часть кода, которая выносит принятое обратно в com.
    Код (C++):
    void loop() {
       input_string="";
       while (Serial.available()) {
          char c=Serial.read();
          if (c == '\n') { //считается, что команда завершена, если пришел символ перевода строки
             break;
          } else {
             input_string += c;
          }
       }
       if (input_string.length()>0) {
           Serial.println(input_string);
           switch ( parse(input_string, Led_off, Led_on) ) {
              case 10:
                  digitalWrite(led, LOW);
                  break;
              case 11:
                  digitalWrite(led, HIGH);
                  break;
              case 9:
                  Serial.println("invalid String");
                  break;
           }
        }
    }

    byte parse(String input_string, const String Led_off, const String Led_on) {
        if (input_string.compareTo(Led_off) == 0) {
           return 10;
        }
        else if (input_string.compareTo(Led_on) == 0) {
           return 11;
       }
       else return 9;
    }
    Это строка идет первой, после завершения while. И в коде она одна - следовательно, если строка выдавалась в ком несколько раз, и несколько же раз отрабатывал switch case, то строка принималась не полностью и несколько раз срабатывало условие
    if (c == '\n') { //считается, что команда завершена, если пришел символ перевода строки
    break;

    Ведь так?
    А если попробовать заменить пробелы нижним подчеркиванием? Может ли быть такое, что пробел воспринимался символом переноса?
    UPD. Нет, дело не в этом.
    UPD2. А между тем, если при проверке длинны строки поставить 13, то условие не сработает, и ком останется пустым. Т.е. косяк в коде, который принимает данные из ком порта.
    UPD3. Как на счет размера буфера последовательного порта? Да хотя нет, 64 байта вроде
    UPD4. А если принимать приходящие байты не чаром, а интеджером? Вообще монитор com порта arduino ide что туда отправляет? char или int?
     
    Последнее редактирование: 11 ноя 2015
  19. Vetrinus

    Vetrinus Гик

    Короче есть у меня подозрение, что дело в while.. попробую его заменить.
    UPD. Нет, while предоставил железное алиби =\
     
    Последнее редактирование: 11 ноя 2015
  20. Megakoteyka

    Megakoteyka Оракул Модератор

    Вам нужно начинать парсить строку в тот момент, когда завершается ее прием (приходит \n). После обработки строки нужно ее обнулить и начать собирать заново.
    Код (C++):
    if(Serial.available())
    {
      char c = Serial.read();
      if(c == '\n')
      {
        parse(str);
        str = "";
      }
      else
        str += c;
    }
     
    Vetrinus нравится это.