Разбор тестовой строки

select2
Offline
Зарегистрирован: 31.10.2012

Здравствуйте, коллеги!

Использую Ethernet-shield. Пытаюсь разобрать параметр, кторый передается в URL, например так: http://192.168.3.5/?2:1,15:0,11:2 . Тут 192.168.3.5 - это адрес шилда. Сервер работает. Привожу код:

 char* param;
  char* buffer;

  if (param = ethernet.serviceRequest())
  {
    ethernet.print("<H2>");
    ethernet.print(param);
    ethernet.print("</H2>");

    buffer=strtok(param,",");
    ethernet.print("<br>");  
    ethernet.print(buffer);
    ethernet.print("</br>");    

    //    while(buffer!=NULL);
    {
      buffer=strtok(NULL,",");
      //      num  =atoi(buffer);
      ethernet.print("<br>");  
      ethernet.print(buffer);
      ethernet.print("</br>");    
    }  

    ethernet.respond();
  }

 ethernet.print(buffer) в строке 07 выводит ?2:1,15:0,11:2. Т.е. параметр передается. А вот парсинг дальше не работает как мне нужно. Я хочу распарсить параметры при помощи функции strtok. При первом и втором вызове она отрабатывает как положено, но вот заставить работать ее в цикле

while(buffer!=NULL);

я не могу - при отображении страницы Arduino виснет до reset'a. Подозреваю, что не работает условие buffer=NULL и она уходит в бесконечны цикл. Работать с указателями я не умею. Прошу помощи.

 
select2
Offline
Зарегистрирован: 31.10.2012

Вот так работает.

void loop() {

  unsigned long time1, time2;
  time1 = micros();

  char* param;
  if (param = ethernet.serviceRequest())
  {
    ethernet.print("<H2>");
    ethernet.print(param);
    ethernet.print("</H2>");

    ethernet.print("<br>");  
    ethernet.print("Output state is:");
    ethernet.print(curr_output_state);
    ethernet.print("</br>");    

    param++;
    param=strtok(param,",");
    ethernet.print("<br>");  
    ethernet.print(param);
    ethernet.print("</br>");    

    for (int i = 0; i < 4; i = i + 1)
    {
      param=strtok(NULL,",");
      ethernet.print("<br>");  
      ethernet.print(param);
      ethernet.print("</br>");    
    }

    ethernet.print("<br>");  
    ethernet.print("Time is:");
    time2 = micros();
    ethernet.print(time2-time1);
    ethernet.print("</br>");    

    ethernet.respond();
  }

Но это не цикл while, а тупой for на фиксированное число параметров. И, естетственно, если параметров больше, чем 4, то он их проигнорирует, а если меньше - то выдает так:

2:1

15:0

11:2

а

а

Time is:4656

Как все-таки правильно использовать strtok?

select2
Offline
Зарегистрирован: 31.10.2012

Сделал так:

    ethernet.print("<br>");  
    ethernet.print("Output state is:");
    ethernet.print(curr_output_state);
    ethernet.print("</br>");    

    param++;
    param=strtok(param,",");
    ethernet.print("<br>");  
    ethernet.print(param);
//    ethernet.print("</br>");    

//    for (int i = 0; i < 4; i = i + 1)
    do
    {
      param=strtok(NULL,",");
      ethernet.print("<br>");  
      ethernet.print(param);
//      ethernet.print("</br>");    
    }
    while(param!=NULL);

    ethernet.print("<br>");  
    ethernet.print("Time is:");
    time2 = micros();
    ethernet.print(time2-time1);
    ethernet.print("</br>");    

    ethernet.respond();

И все равно добавляется одна дополнительная итерация цикла (видно как 'a'):

Output state is:-32768

2:1

15:0

11:2

2:1

15:0

а

Time is:4560

 

Что делать? Подскажите?

leshak
Offline
Зарегистрирован: 29.09.2011

>например так: http://192.168.3.5/?2:1,15:0,11:2

Это потому что вы деалете  ethernet.print(param); 

не проверяя, вернул вам strtok NULL или нет.

Вам нужно, либо

 


    do
    {
      param=strtok(NULL,",");
      if(param!=NULL){
         ethernet.print("<br>");  
         ethernet.print(param);
      }

    }
    while(param!=NULL);

Но, как на меня - выглядит коряво. Лучше вернутся к вашей изначальной идее с while циклом. И поправить ее - перенести strok в конец цикла.

char* param;
 char* buffer;

 if (param = ethernet.serviceRequest())
 {
   ethernet.print("<H2>");
   ethernet.print(param);
   ethernet.print("</H2>");

   buffer=strtok(param,",");
   ethernet.print("<br>");  
   ethernet.print(buffer);
   ethernet.print("</br>");    

    while(buffer!=NULL);
   {
    
     //      num  =atoi(buffer);
     ethernet.print("<br>");  
     ethernet.print(buffer);
     ethernet.print("</br>");    
     buffer=strtok(NULL,",");
   }  

   ethernet.respond();
 }

 

leshak
Offline
Зарегистрирован: 29.09.2011

Эм, второй вариант нужно еще сократить, что-бы не делать print если даже  первого токена не нашло.

char* param;
 char* buffer;

 if (param = ethernet.serviceRequest())
 {
   ethernet.print("<H2>");
   ethernet.print(param);
   ethernet.print("</H2>");

   buffer=strtok(param,",");

    while(buffer!=NULL);
   {
    

     ethernet.print("<br>");  
     ethernet.print(buffer);
     ethernet.print("</br>");    
     buffer=strtok(NULL,",");
   }  

   ethernet.respond();
 }

 

leshak
Offline
Зарегистрирован: 29.09.2011

А можно попытатся так поизголятся  (но я не уверен что такой код можно назвать красивым), сделать это в виде for-а. Правда не проверял (нет щас ArduinoIDE под руками) 

char* param;
 char* buffer;

 if (param = ethernet.serviceRequest())
 {
   ethernet.print("<H2>");
   ethernet.print(param);
   ethernet.print("</H2>");

    for(buffer=strtok(param,","); buffer!=NULL;     buffer=strtok(NULL,",") )
   {
       ethernet.print("<br>");  
       ethernet.print(buffer);
       ethernet.print("</br>");    
   }  

   ethernet.respond();
 }

 

select2
Offline
Зарегистрирован: 31.10.2012

Хм, я знаю, что это из за отсутствия проверки на NULL, но она у меня почему то не отрабатывала.

Ваш вариант с for(..) компилится. Завтра проверю на железке и отпишусь дополнительно.

Спасибо за помощь!

leshak
Offline
Зарегистрирован: 29.09.2011

 

select2 пишет:

Хм, я знаю, что это из за отсутствия проверки на NULL, но она у меня почему то не отрабатывала.

А как она может отрабатывать, если она отсуствует? Если же вы говорите про свой первый вариант (который c while) - то всмотритесь в разницу: вы делали strtok в начале тела цикла, а я - в конце.

 

select2
Offline
Зарегистрирован: 31.10.2012

Супер!

Всё работает как нужно с циклом for.. (пост №5).

Большое спасибо, leshak!

 

select2
Offline
Зарегистрирован: 31.10.2012

В догонку вопрос:

В программе есть переменная, описанная следующим образом:

//Описание глобальных переменных
uint16_t curr_output_state = 0;                        //Состояние выходов

Так вот при попытке вывести ее значение

ethernet.print(curr_output_state);

Я получаю числа больше 32768 как отрицательные. Хотя описана она как беззнаковое целое. Нужно получать целое.

Как быть? Смотреть внутрь ethernet.print?

 

 

select2
Offline
Зарегистрирован: 31.10.2012

В дебрях библиотеки нашел:

void ETHER_28J60::print(int number)
{
  char tempString[9]; 
  itoa(number, tempString, 10);
  print(tempString);
}

И все равно непонятно, где косяк - переопределения типа вроде нет.

toc
Offline
Зарегистрирован: 09.02.2013

Добавьте в библиотеку свой аналoгичный метод print, но с параметром типа uint16_t. Вероятно, нужно найти подходящую замену для функции itoa.

select2
Offline
Зарегистрирован: 31.10.2012

Спасибо!

Буду пробовать - мои познания в С пока оставляют желать лучшего.

toc
Offline
Зарегистрирован: 09.02.2013

может быть ltoa

step962
Offline
Зарегистрирован: 23.05.2011

select2 пишет:

В дебрях библиотеки нашел:

void ETHER_28J60::print(int number)
{
  char tempString[9]; 
  itoa(number, tempString, 10);
  print(tempString);
}

И все равно непонятно, где косяк - переопределения типа вроде нет.

Переопределение - уж простите за тавтологию - в самом определении функции. Те два байта, что вы передаете при вызове, она воспринимает как двухбайтовое целое со знаком (int). Так что либо ищите функцию print, принимающую параметр long int (ну и соответственно передавайте ей аргумент long int), либо пишите свою, принимающую параметр unsigned int.

Либо - в том случае, если в библиотеке есть перегруженная функция print(unsined int), вызывайте именно ее.

Что-то вроде

ethernet.print((unsigned int)curr_output_state);

 

leshak
Offline
Зарегистрирован: 29.09.2011

Я бы не лез что-то менять в библиотеку без крайней нужды. Выйдет новая версия - и опять меняй, даш кому-то код - будет ругатся.

imho лучше вначале конвертировать ваш Unsigned int в строку, а уж строку - кормить библиотеке.

Я бы поптылся сделать это с помощью sprintf. Правда пару раз натыкался, что в арудино он полукоцнутый (в целях экономии), с double не работал вроде, нужно пробовать как с uint16 он справится. Если справится - то возможно даже удобней будет, обычно же не просто число, а еще и какоей-то текст выводить нужно с ним поясняющий. sprintf в этом очень удобен.

P.S. Судя по куску из либы , вы юзаете библиотеку enc28j60 имитирующую интерфейс визнетовской библиотеки. Я находил, годик назад, вот такую https://github.com/turicas/Ethernet_ENC28J60/ но была очень сырой и автор забросил. Как ваша, стабильно работает? Это "она позрослела" или у вас какая-то другая? Если другая - ссылкой не поделитесь?

select2
Offline
Зарегистрирован: 31.10.2012

Leshak, по поводу библиотеки: именно по ссылке на этом форуме по вашей рекомендации брал на gthub'e. Точный URL не помню, но в библиотеке в заголовке написано следующее:

ETHER_28J60.cpp - Ethernet library
Copyright (c) 2010 Simon Monk.  All right reserved.
Работает на удивление очень хорошо и просто. И быстро. И размер скетча не сильно вырос. И это не библиотека, по приведенной выше ссылке.

Ссылкой, соответственно не поделюсь, но могу выслать архив с библиотекой целиком.

select2
Offline
Зарегистрирован: 31.10.2012

Вернемся к тексту. Вернее к разбору параметров. Напомню: параметр передается в виде http://192.168.3.5/?12:1,15:0,11:2,2:1,77:4

Т.е. пары "порт:значение" , разделенные зяпятыми. С помощью leshak http://arduino.ru/forum/programmirovanie/razbor-testovoi-stroki#comment-27718 я выделил пары. Теперь мне необходимо выделить в каждой паре "порт:значение" "порт" и "значение". И тут я попадаю в тупик. Видимо не до конца понимая разницу работы с char и char * (указатель). Вот код:

void loop() {

  RealTimer.run();                                     // Тут делать нечего - только инициируем таймер 8)

  if (param = ethernet.serviceRequest()) web();        // Если есть URL-запрос, то обработаем его

}
//END OF PROGRAM

void web()
{
  unsigned long time1, time2;
  time1 = micros();
  int port, value;
  char sym;

  //    Serial.write(param);

  param++;                                             //Пропустим первый "?" в URL'е - он нам не нужен.
  //
  for(buffer=strtok(param,","); buffer!=NULL; buffer=strtok(NULL,",") )
  {
    ethernet.print("<br>"); 
    ethernet.print(buffer);
    }
  }  
  param[0]=0;                                           //Очистим после обработки глобальный буфер строки параметров
  ethernet.respond();
}


В строке 24 ethernet.print(buffer); корректно выводятся пары "порт:значение", но если я пытаюсь по аналогии разобрать пары "порт:значение" при помощи функции strtok, то она разрушает исходную строку. Как передать строку в функцию, чтобы передавалась копия? У меня передается указатель и манипуляции со строкой разрушают ее основную копию. Как сделать это правильно?

 

select2
Offline
Зарегистрирован: 31.10.2012
  for(buffer=strtok(param,","); buffer!=NULL; buffer=strtok(NULL,",") )
  {
    ethernet.print("<br>"); 
    ethernet.print(buffer);
    buffer3=buffer;
    ferr = false;
    buffer2=strtok(buffer,":");
    
//    sym=strtoul(buffer,NULL,0);
    ethernet.print("------"); 
    ethernet.print(buffer2);
//    sym=strtoul(buffer,NULL,0);
    buffer2=strtok(NULL,":");
    ethernet.print("--:--"); 
    ethernet.print(buffer2);
    buffer=buffer3;
  }

Этот фрагмент при вызове http://192.168.3.5/?12:1,15:0,11:2,2:1,15,77:4 выдает

12:1------12--:--1
Output state is:-16642

Time is:200

Т.е. после первого вызова buffer2=strtok(buffer,":"); портит исходную строку. Ну не знаю что делать уже!

select2
Offline
Зарегистрирован: 31.10.2012

Плюнул на все функции и написал разбор сам. Правда без проверок на корректность, но размер скетча уменьшился сильно, по сравнению с применением atoul и т.п.

  param++;                                             //Пропустим первый "?" в URL'е - он нам не нужен.
  //
  for(buffer=strtok(param,","); buffer!=NULL; buffer=strtok(NULL,",") )
  {
    ethernet.print("<br>"); 
    ethernet.print(buffer);

    port = (*(buffer) - 48);
    if (*(buffer+1)!=':') {
      port = port *10 + (*(buffer+1)-48);
      value = (*(buffer+3)-48); 
    }
    else value = (*(buffer+2)-48);
    ethernet.print("==>");
    ethernet.print(port); 
    ethernet.print("==>");    
    ethernet.print(value);  
  }

Может все-таки есть более красивое решение? Меня смущает в этой конструкции время обработки - почти 1000мкс на 7 параметров..

p.s. Таки разобрался с указателями.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Нужно смотреть описание функции strtok, на ней вложенный разбор будет сделать сложно, поскольку эта функция (strtok) работает со статическим буфером внутри себя. Вы нашли другой вариант - прекрасно!

 

leshak
Offline
Зарегистрирован: 29.09.2011

select2 пишет:

 

В строке 24 ethernet.print(buffer); корректно выводятся пары "порт:значение", но если я пытаюсь по аналогии разобрать пары "порт:значение" при помощи функции strtok, то она разрушает исходную строку. Как передать строку в функцию, чтобы передавалась копия? У меня передается указатель и манипуляции со строкой разрушают ее основную копию. Как сделать это правильно?

IMHO вы путь неверный выбрали. Вам нужно не "разбил строку на части по признаку запятая", а потом в этих частях искать двоеточия. А изначально идти "ищем двоеточие"/"ищем запятую"/"ищем двоеточие"/"ищем запятую",

вообщем по очереди дергать strtok(NULL,':') и  strtok(NULL,',') . То что вернулось от первого - ваш порт, то что от второго - значение. И все :) Телемаркет.

leshak
Offline
Зарегистрирован: 29.09.2011

Если бы я писал, то во первых разделил бы сам парсинг и вывод того что напарсили. Мухи отдельно, котлеты отдельно. А то, потом, у вас в парнсинг и бизнес логика влезет. А потом "черт ногу сломит".

Во вторых - вынес бы парсинг в отдельную функцию. Которая ничерта не знает "откуда взялась строка" из ether-нета или от телепатов. Так и тестить и на другое железо легче перейти будет (и другим, кстати легче будет ваш скетч пускать, даже тем у кого нет вашего шилда).

Вообщем примерно так:

// структра описывающие наш параметр
struct port_param_t{
    int port;
    int value;
};

#define MAX_PARAMS 20 // сколько параметров максимально мы умеем парсить
port_param_t params[MAX_PARAMS]; // в этот массис будем сохранять наши парсенные параметры
byte parsedParams=0; // сколько параметров нам удалось напарсить

void setup(){
  Serial.begin(57600);
  
  char* inputString="h ttp://192.168.3.5/?2:1,15:0,11:2"; // наши тестовые данные
  
  // выводим что собираемся парсить
  Serial.print("input '");Serial.print(inputString);  Serial.println("'");
  
  // парсим
  parseParams(inputString);
  
  // выводим что получилось
  printParams();
  
}



// парсид входящую строку в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parseParams(char* inputString){
  parsedParams=0; // пока ничего не напарсили
  
  char* buffer=strtok(inputString,"?"); // лучше так проверять/пропускать вопросилово
  
  if(buffer!=NULL){ 
    for(buffer=strtok(NULL,":"); buffer!=NULL;     buffer=strtok(NULL,":") )   {
         // парсим порт
         params[parsedParams].port=atoi(buffer); // вам же навернео еще и сразу в int захочется конвертнуть, раз это цифры
         
        // парсим значение
         if( (buffer=strtok(NULL,",")) !=NULL)   params[parsedParams].value=atoi(buffer);
         else return ; // фигня какая-то, порт есть, а значения нет, прекращаем парсинг
         
         parsedParams++; // отмечаем сколько удалось распарсить
         if(parsedParams>MAX_PARAMS-1)return; // больше нет места куда сохранять парсенное.

     }  
  
  }
  
}

// Выводит в Serial массив parsedParams[]
void printParams(){
  for(byte i=0;i<parsedParams;i++){
      // TODO: всю эту кучу принтов можно заменить одним sprintf
      Serial.print("Port");Serial.print(params[i].port,DEC);
      Serial.print("=");
      Serial.println(params[i].value,DEC);
  }
}

void loop(){
}

Выведет:

input 'h ttp://192.168.3.5/?2:1,15:0,11:2';
Port2=1
Port15=0
Port11=2

 

select2
Offline
Зарегистрирован: 31.10.2012

Leshak - просто нет слов! Красиво, черт побери!

И идея красивая (про "," и ":"). И структурно (и в программе у меня) правильнее вынести парсинг и вывод в отдельные процедуры. Я вот тоже хотел разделить, но не нашел красивого метода, как хранить реультат парсинга. Оригинально - со структурой получилось.

Я вот и хотел у зубров такого вот плана совет.

Но больше всего меня поражает, что вы нашли время набрать и проверить текст программы. Еще и с комментариями! Спасибо огромное!

Я снимаю шляпу. Будете в Омске - пожалуйста откликнитесь- надеюсь передать поклон лично.

 

p.s. Еще про atoi - уж больно много она памяти жрет. Я наверное свой примитивный конвертор оставлю.

leshak
Offline
Зарегистрирован: 29.09.2011

>но не нашел красивого метода, как хранить реультат парсинга

Ну вообще структуры - довольно мутная штука. Пару раз наступал на глюки с ними в ардуине. Но даже не зная их, можно было просто объявить два массива int ports[MAX_PARAMS] и int values[MAX_PARAMS] .

 

>Будете в Омске - пожалуйста откликнитесь- надеюсь передать поклон лично.

"Интернет открывает весь мир и запирает в одной комнате" :)  Мне и на маршрутке проехать 5-ть остановок - уже событие жизни :)

>Но больше всего меня поражает, что вы нашли время набрать и проверить текст программы

На что только не пойдешь лишь бы свою основную работу не делать :)

>Еще про atoi - уж больно много она памяти жрет

Ну а что мешает написать свой ее вариант. Более урощенный? Тем более что принцип его построения - вы сами догадались в "своем парсере". Осталось только вынести его в отдельную функцию что-бы не дублировать код при парсинге порта и значения. Да и привязываться к "сколько цифр ожидается" - не сдорово. Лучше универсальную. Вообщем вот:

#define PARAM_DELIMETER "," // чем разделены у нас параметры
#define NAME_VALUE_DELIMETER ":" // чем разделены у нас имя параметра и значение

// структра описывающие наш параметр
struct port_param_t{
    byte port;
    byte value;
};

#define MAX_PARAMS 20 // сколько параметров максимально мы умеем парсить
port_param_t params[MAX_PARAMS]; // в этот массис будем сохранять наши парсенные параметры
byte parsedParams=0; // сколько параметров нам удалось напарсить

void setup(){
  Serial.begin(57600);
  
  char* inputString="h ttp://192.168.3.5/?2:1,15:0,11:2"; // наши тестовые данные
  
  // выводим что собираемся парсить
  Serial.print("input '");Serial.print(inputString);  Serial.println("'");
  
  // парсим
  parseParams(inputString);
  
  // выводим что получилось
  printParams();
  
}



// парсид входящую строку в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parseParams(char* inputString){
  parsedParams=0; // пока ничего не напарсили
  
  char* buffer=strtok(inputString,"?"); // лучше так проверять/пропускать вопросилово
  
  if(buffer!=NULL){ 
    for(buffer=strtok(NULL, NAME_VALUE_DELIMETER); buffer!=NULL;     buffer=strtok(NULL, NAME_VALUE_DELIMETER) )   {
         // парсим порт
         params[parsedParams].port=parseByte(buffer); // вам же навернео еще и сразу в int захочется конвертнуть, раз это цифры
         
        // парсим значение
         if( (buffer=strtok(NULL,PARAM_DELIMETER)) !=NULL)   params[parsedParams].value=parseByte(buffer);
         else return ; // фигня какая-то, порт есть, а значения нет, прекращаем парсинг
         
         parsedParams++; // отмечаем сколько удалось распарсить
         if(parsedParams>MAX_PARAMS-1)return; // больше нет места куда сохранять парсенное.

     }  
  
  }
  
}

// Выводит в Serial массив parsedParams[]
void printParams(){
  for(byte i=0;i<parsedParams;i++){
      // TODO: всю эту кучу принтов можно заменить одним sprintf
      Serial.print("Port");Serial.print(params[i].port,DEC);
      Serial.print("=");
      Serial.println(params[i].value,DEC);
  }
}

byte parseByte(char* inputStr){
  byte val=0;
  while( (*inputStr)>='0' && (*inputStr)<='9'){ // идем по строке пока у нас цифры попадаются
     val=val*10+((*inputStr++)-'0'); // дописываем ноль к текущем val (десятичный сдвиг влево) и прибавляем новую цифру, и сдвигаемся к следующему символу

  }
  return val;
}

void loop(){
}

Разница с прошлым скетчем:

1. atoi заменен на parseByte()  
2. В структуре int-ты заменил на byte  (я так понял больших чисел у нас нет), а память экономить вы хотите :)
3. Вынес чем разделяются параметры и значение в дефайны (первые две строки). Что-бы потом легче было применить для парсинга нормальных урлов вида htt p://domain.com/?param1=value1&param2=value&param3=value  . Заменил в дефане запяты и двоеточия на равно и амперсанд - и не нужно по коду лазить.

select2
Offline
Зарегистрирован: 31.10.2012

Найденные мной ошибки:

Cтрокa 39 должна выглядеть так:

    for(buffer=strtok(buffer, NAME_VALUE_DELIMETER); buffer!=NULL; buffer=strtok(NULL, NAME_VALUE_DELIMETER) )   {

Первый раз в skrok нужно указать строку. Без этого не работало.

И есть еще логическая ошибка. Сразу заподозрил, и она проявилась: при разборе строки "http://192.168.3.5/?1:12,3,6:2". Ошибки не выдает. А вывод такой:

Port1=12

Port3=2

AllOK! Out state:0

Т.е. пропускает просто порт до следующего параметра и все! Видимо все-таки нужно сначала парсить на пары port:value, а потом парсить отдельно каждую  пару.

leshak
Offline
Зарегистрирован: 29.09.2011

>Первый раз в skrok нужно указать строку.

Не нужно. Первый раз мы строку уже указали в строке 36

>Без этого не работало.

А вы не руками скетч перенабирали? Сейчас взял скетч из #24. Залил. Вывод полностью идентичен тому что было до поправок. То есть логика по сравнению с #22 - не изменилась.

>И есть еще логическая ошибка

Ну это ошибкой с натяжкой можно назвать. Просто у вас получилась пара порт:значение "3,6:2"  . То есть port с номером 3,6. parseByte попытался это "3,6" распарсить. И распарсил "как смог" до первого не цифрового символа. Вышло 3.

Так что это скорее не ошибка, а "можно еще доп.проверки на неправильные входные данные добавить". Но... если довить проверку всех возможных несуразностей, но... размер?  Мы отказывлись от atoi для уменьшения размера. А для этого делаются какие-то предположения-упрощения-ожидания.

Можно вообще, перед тем как вообще парсить проверять сколько у нас двоеточий, сколько равенств. Вообщем тут уже от ситуации зависит насколько это все нужно. 

>Cтрокa 39 должна выглядеть так:

Ну, как я говорил выше - не обязательно :) Так как она есть - тоже работает. Если уже пытатся ее упрощать - то отказом от for и переход на while.  когда for у нас появился мы еще не делали проверку на "?". И первый и третий параметра for- отличались. Так как они перестали отличатся - for - уже избыточен. И то же самое можно сделать так:

 while( (buffer=strtok(NULL, NAME_VALUE_DELIMETER))!=NULL ) {

 

select2
Offline
Зарегистрирован: 31.10.2012

Сделал, как писал в пердыдущем посте: выделил разбор на пары "Порт:значение" и разбор внутри пары. Наступил на грабли, как и в прошлый раз: разрушается строка в подпрограмме parsePortValue.

//Процедура обработки http-запроса
void web()
{
  unsigned long time1, time2;
  time1 = micros();
  ferr = false;

  parseParams(param);                                   // Парсим полученный URL

  printParams();                                        // выводим что получилось
  /*

   for(buffer=strtok(param,","); buffer!=NULL; buffer=strtok(NULL,",") )
   {
   //    ethernet.print("<br>"); 
   //    ethernet.print(buffer); 
   //В buffer находится строка "порт:значение". Заносим из нее значения в переменные port и value
   //ПРОВЕРКИ НА КОРРЕКТНОСТЬ НЕТ!!! Необходимо проверять корректность при заполнении строки!
   //Предполагаю, что порт может быть двух- или однозначным, значение - однозначным.
   port = (*(buffer) - 48);                             //Первая цифра - номер порта
   if (*(buffer+1)!=':') {                              //Если второй символ так же цифра, то
   port = port *10 + (*(buffer+1)-48);                //первый был - десятки, добавим единицы
   value = (*(buffer+3)-48);                          //Четвертый, в таком случае - состояние порта
   }
   else value = (*(buffer+2)-48);                       //Если второй символ - разделитель ':', тогда третий - состояние порта
   if (port > 64)ferr = true;                           //Проверка на корректность введенных значений номера порта
   if (value > 2)ferr = true;                           //Проверка на корректность введенных значений параметра порта
   //Управление нагрузкой
   if (ferr==false){                                    //Если прочитанне значения корректны, то применим их
   switch (value) {
   case 0:
   DisableOutputState(port);
   break;
   case 1:
   EnableOutputState(port);
   break;
   case 2:
   ToggleOutputState(port);
   break;
   default:
   break;
   }
   }
   
   
   //Вывод отладочной информации
   //
   ethernet.print("<BR>");
   ethernet.print(port); 
   ethernet.print("==>");    
   ethernet.print(value);  
   if (ferr) ethernet.print("ERROR!");    
   }
   ethernet.print("<br>");  
   */

  if (ferr) ethernet.print("ERROR!");
  else ethernet.print("AllOK!");
  ethernet.print(" Out state:");
  ethernet.print(curr_output_state);
  ethernet.print("</br>");    

  ethernet.print("<br>");  
  ethernet.print("Time is:");
  time2 = micros();
  ethernet.print(time2-time1);

  param[0]=0;                                           //Очистим после обработки глобальный буфер строки параметров
  ethernet.respond();
}

// парсит входящую строку в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parseParams(char* inputString){
  parsedParams=0;                                             // пока ничего не напарсили, число распознанных переменных = 0
  char* buffer=strtok(inputString,"?");                       //Параметры для разбора команды в URL'e. Пропустим "?" в URL'е - он нам не нужен.

  if(buffer!=NULL){                                           //Бъем на пары "Порт:значение", разделенные PARAM_DELIMETER
    for(buffer=strtok(buffer, PARAM_DELIMETER); buffer!=NULL; buffer=strtok(NULL, PARAM_DELIMETER) )   {
      // парсим значения
  ethernet.print(buffer);
  ethernet.print("<br>");
      parsePortValue(buffer);
  ethernet.print(buffer);
  ethernet.print("<br>");
      //
      //      ethernet.print(params[parsedParams].port);
      //
      // парсим значение
    }
    parsedParams++;                                         // отмечаем сколько удалось распарсить
    if(parsedParams>MAX_PARAMS-1){                          // больше нет места куда сохранять парсенное.
      ferr = true; 
      return; 
    }
  }  
}


// Выводит в Serial массив parsedParams[]
void printParams(){
  for(byte i=0;i<parsedParams;i++){
    // TODO: всю эту кучу принтов можно заменить одним sprintf
    ethernet.print("Port");
    ethernet.print(params[i].port);
    ethernet.print("=");
    ethernet.print(params[i].value);
  }
}

byte parseByte(char* inputStr){
  byte val=0;
  while( (*inputStr)>='0' && (*inputStr)<='9'){ // идем по строке пока у нас цифры попадаются
    val=val*10+((*inputStr++)-'0'); // дописываем ноль к текущем val (десятичный сдвиг влево) и прибавляем новую цифру, и сдвигаемся к следующему символу

  }
  return val;
}


// парсит распознанную пару "port:value" в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parsePortValue(char* inputString){
  char* buffer=strtok(inputString,":");                     //Достанем значение перед ":". Это port
  if(buffer!=NULL){                                         //Получилось?
    // парсим порт  
    params[parsedParams].port=parseByte(buffer);            //Тогда конвертим в int
    // парсим значение
    if( (buffer=strtok(NULL,PARAM_DELIMETER)) !=NULL)       //Идем дальше. Получилось?
      params[parsedParams].value=parseByte(buffer);         //Тогда это значение
    else {                                                  // фигня какая-то, порт есть, а значения нет, прекращаем парсинг
      ferr = true;
      return ;
    }
  } 
  else ferr = true; 
}

На выводе при строке "http://192.168.3.5/?51:12,3:2" имеем:

51:12
51
Port51=12AllOK! Out state:0

Time is:236

Т.е. строка разрушилась после вызова parsePortValeu. Что-то я никак не догоню, как работает strtok.

select2
Offline
Зарегистрирован: 31.10.2012

leshak пишет:

>Cтрокa 39 должна выглядеть так:

Ну, как я говорил выше - не обязательно :) Так как она есть - тоже работает. Если уже пытатся ее упрощать - то отказом от for и переход на while.  когда for у нас появился мы еще не делали проверку на "?". И первый и третий параметра for- отличались. Так как они перестали отличатся - for - уже избыточен. И то же самое можно сделать так:

 while( (buffer=strtok(NULL, NAME_VALUE_DELIMETER))!=NULL ) {

Leshak, у меня не работало в исходном варианте - выдавало сразу 0 и не разбирало строку. После сделанной мной замены программа заработала как нужно.

Про ошибку логическую - понял. В принципе согласен. Но ловить то ошибку нужно? А то можно вместо света в ванной комнате отопление выключить 8). И узнать об этом только завтра.

Я попробую сам, ладно? А потом критикнете мои потуги..

leshak
Offline
Зарегистрирован: 29.09.2011

Таки разрушает, негодяй. Заплевывает ее нулевыми символами.

Вот, возможно вот это поможет втыкнуть "что он творит".

Функция dumpString - выводит строку, нулевой символ показывается звездочкой, и в следующей строке рисует где у нас сейчас указатель который вернул strkok

 

  #define PARAM_DELIMETER "," // чем разделены у нас параметры
  #define NAME_VALUE_DELIMETER ":" // чем разделены у нас имя параметра и значение
  

byte len;
void setup(){
  Serial.begin(57600);
  
  char* inputString="1234567890abc";
  char* pointer=inputString;
  len=strlen(inputString);

  dumpString("[init]",inputString,pointer);
  
  // сдвигаем поинтер на три символа что-бы увидить как работает наш dump
  pointer+=3;
  dumpString("[+3]",inputString,pointer);
  
  // теперь пробуем поигратся с stkok
  
  pointer=strtok(inputString,"2");
  dumpString("[inputString,2]",inputString,pointer);
  

  pointer=strtok(NULL,"4");
  dumpString("[NULL,4]",inputString,pointer);
  
  pointer=strtok(NULL,"6");
  dumpString("[NULL,6]",inputString,pointer);

}


void loop(){
}

// выводим строку в кодах, не обращая внимание на нулевые символы
void dumpString(char* comment, char* input,char* p){
  
  // выводим коментарий что это за строка
  Serial.println();
  Serial.println(comment); 
  
  for(byte i=0;i<=len;i++){
    char ch=*(input+i); // текущий символ
    if(ch)Serial.write(ch);
    else Serial.write('*'); // вместу нулевого - выводим * что-бы видеть его 
  }
  Serial.println();
  // На второй стороке символом | покажем текущую позицию указателя p
  
  for(byte i=0;i<=len;i++){
    char* pos=input+i;
    if(pos==p)Serial.write("|");
    else Serial.write(' ');
  }
  
  Serial.println();
  
}

Выводит

[init]
1234567890abc*
|             

[+3]
1234567890abc*
   |          

[inputString,2]
1*34567890abc*
|             

[NULL,4]
1*3*567890abc*
  |           

[NULL,6]
1*3*5*7890abc*
    |         

 

Можете попробовать поэксперементировать с разными строками и вызовами strok и смотреть "что сейчас у нас в строке" и куда счас указатель указывает :)  (соответсвенно дальнейший парсинг - будет идти правее него).

 

leshak
Offline
Зарегистрирован: 29.09.2011

 

select2 пишет:

 

Leshak, у меня не работало в исходном варианте - выдавало сразу 0 и не разбирало строку. После сделанной мной замены программа заработала как нужно.

не верю. чудес не бывает. значит вы внесли еще какие-то изменения в код из #24. Я вот взял его, из форума, не откуда-то из своего файла. Скопировал и запустил, не меняя ни одной буквы - работает.

> не работало в исходном варианте

Вы так и не ответили. Насколько этот вариант "исходный"? Вы руками его набирали или копировали через кнопку в верхнем правом углу кода?

leshak
Offline
Зарегистрирован: 29.09.2011

>не работало в исходном варианте

Ну вот смотрите, я взял код из #24 и выкинул структуры, массивы, парсинг чисел т.п. просто разбиваю на токены и сразу вывожу обыкновенным Serial.print, что-бы видеть "как строку разбило".

 

#define PARAM_DELIMETER "," // чем разделены у нас параметры
#define NAME_VALUE_DELIMETER ":" // чем разделены у нас имя параметра и значение



void setup(){
  Serial.begin(57600);
  
  char* inputString="h ttp://192.168.3.5/?2:1,15:0,11:2"; // наши тестовые данные
  
  // выводим что собираемся парсить
  Serial.print("input '");Serial.print(inputString);  Serial.println("'");
  
  // парсим
  parseParams(inputString);
  

  
}


// парсид входящую строку в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parseParams(char* inputString){
  
  
  char* buffer=strtok(inputString,"?"); // лучше так проверять/пропускать вопросилово
  
  if(buffer!=NULL){ 
    for(buffer=strtok(NULL, NAME_VALUE_DELIMETER); buffer!=NULL;     buffer=strtok(NULL, NAME_VALUE_DELIMETER) )   {
         // парсим порт
         Serial.print("port=");Serial.println(buffer);
         
        // парсим значение
         if( (buffer=strtok(NULL,PARAM_DELIMETER)) !=NULL)   {
           Serial.print("value=");Serial.println(buffer);
         }
         else return ; // фигня какая-то, порт есть, а значения нет, прекращаем парсинг

     }  
  
  }
  
}

void loop(){}

Выводит оно такое:

input 'h ttp://192.168.3.5/?2:1,15:0,11:2'
port=2
value=1
port=15
value=0
port=11
value=2

Теперь вносим ваше "исправление" 

#define PARAM_DELIMETER "," // чем разделены у нас параметры
#define NAME_VALUE_DELIMETER ":" // чем разделены у нас имя параметра и значение



void setup(){
  Serial.begin(57600);
  
  char* inputString="h ttp://192.168.3.5/?2:1,15:0,11:2"; // наши тестовые данные
  
  // выводим что собираемся парсить
  Serial.print("input2 '");Serial.print(inputString);  Serial.println("'");
  
  // парсим
  parseParams(inputString);
  

  
}


// парсид входящую строку в массив params[] и устанавливает parsedParam в количество прочитанных элементов
void parseParams(char* inputString){
  
  
  char* buffer=strtok(inputString,"?"); // лучше так проверять/пропускать вопросилово
  
  if(buffer!=NULL){ 
    for(buffer=strtok(buffer, NAME_VALUE_DELIMETER); buffer!=NULL; buffer=strtok(NULL, NAME_VALUE_DELIMETER) )   {

         // парсим порт
         Serial.print("port=");Serial.println(buffer);
         
        // парсим значение
         if( (buffer=strtok(NULL,PARAM_DELIMETER)) !=NULL)   {
           Serial.print("value=");Serial.println(buffer);
         }
         else return ; // фигня какая-то, порт есть, а значения нет, прекращаем парсинг

     }  
  
  }
  
}

void loop(){}

Выводит:

input2 'h ttp://192.168.3.5/?2:1,15:0,11:2'
port=h ttp
value=//192.168.3.5/

Вотзьмите эти два скетча. Пустите у себя и запостите тут что выводит первый и что выводит второй

Апрайсин
Апрайсин аватар
Offline
Зарегистрирован: 05.08.2013

А подскажите пожалуйста. Я работаю с JSM и принимаю строку
 

+CMT: "+380999******","","13/10/04,15:17:46+

Мне из этой строки нужно изьять номер телефона.

Вот как я пробую делать


#include <SoftwareSerial.h>


SoftwareSerial gprsSerial(10, 11);

char currSymb;
byte buff[255];
byte b;
String num1 = "";
String num = "";
String inString;

void setup()
{   
    Serial.begin(9600);
    gprsSerial.begin(9600);
    // Настраиваем приём сообщений с других устройств
    // Между командами даём время на их обработку
    gprsSerial.print("AT+CMGF=1\r");
    delay(300);
    gprsSerial.print("AT+IFC=1, 1\r");
    delay(300);
    gprsSerial.print("AT+CPBS=\"SM\"\r");
    delay(300);
    gprsSerial.print("AT+CNMI=1,2,2,1,0\r");
    delay(500);
    gprsSerial.print("AT+CMGDA=«DEL ALL»");
}

String currStr = "";
// Переменная принимает значение True, если текущая строка является сообщением
boolean isStringMessage = false;

void loop()
{
  rec();
  tabl();
  
/*  if (currStr.startsWith("+CMT")) 
  {
    if (currStr == ""){   //!!!!!!!!!!!!!!!
         Serial.print("ok1");
         Serial.print(" ");  
    }
     if (!currStr.compareTo(num1)){   //!!!!!!!!!!!!!!!
         Serial.print("ok2");
         Serial.print(" ");  
}
             } */
  
    if ('\r' == currSymb) {
        if (isStringMessage) {
              //Serial.println("");
              //Serial.println("---");
            //если текущая строка - SMS-сообщение,
            //отреагируем на него соответствующим образом
            if (!currStr.compareTo("123")) {
                Serial.print("321");
            } else if (!currStr.compareTo("Start")) {
                Serial.print("st");
            } else if (!currStr.compareTo("Buff")) {
              Serial.print("");
                for (byte i = 0; i < 255; i ++){ 
               Serial.print(buff[i]);
               Serial.print(" ");
             }
             Serial.println("");
               for (byte i = 0; i < 255; i ++){ 
               Serial.print(buff[i], HEX);
               Serial.print(" ");
             }
            } else if (!currStr.compareTo("Num")) {
               Serial.println("num1");
            } 
            isStringMessage = false;
            } else {
            if (currStr.startsWith("+CMT")) {
                //если текущая строка начинается с "+CMT",
                //то следующая строка является сообщением
                isStringMessage = true;
            }
        }
        currStr = "";
     
    /*gprsSerial.print("AT+CMGR=1,0");
    for (byte i = 0; i < 5; i++) {
    char info = gprsSerial.read();
    Serial.print(info);
    Serial.print(" ");
    }
    Serial.println();*/
     //Serial.println("-/-");
     } else if ('\n' != currSymb) {
        currStr += String(currSymb);
    }
  
}



void rec()
{
    if (gprsSerial.available() > 0)
    {
    b++;
    currSymb = gprsSerial.read();
   /* Serial.print(b);
    Serial.print(" ");
    Serial.print(currSymb);
    Serial.print(" ");
    Serial.print(currSymb, DEC);
    Serial.print(" ");
    Serial.print(currSymb, HEX);
    Serial.println("");*/
    buff[b] = currSymb;
    
    
    int inChar = buff[b];
     inString += (char)inChar; 

    if(buff[b] == 0xA && buff[b-1] == 0xD) 
     {
     b=0;
    // num = buff[10]<<4+buff[11]<<4+buff[12]<<4+buff[13];//<<4+buff[14]<<4+buff[15]<<4+buff[16]<<4+buff[17]<<4+buff[18]<<4+buff[19]<<4+buff[20];
    Serial.print("String: ");
    Serial.println(inString);
 
 unsigned long s1, s2, s3, s4;
 char buffer[50];
  inString.toCharArray(buffer,50);
  s1=atoi(strtok(buffer, " "));
  s2=atoi(strtok(NULL, ","));
  s3=atoi(strtok(NULL, ","));
  s4=atoi(strtok(NULL, ","));
 
    Serial.print("start: ");
    Serial.println(s1);
    Serial.print("num: ");
    Serial.println(s2);
    Serial.print("null: ");
    Serial.println(s3);
    Serial.print("date: ");
    Serial.println(s4);
    
    inString = ""; 
     }
    }

}

В самом конце строчки, которыми я пробую разбить строку.

Чего уже только не пробовал делать, а хочеться чтобы было красиво и этот номер можно было сравнить с заведомо забитым в ардуину номером.