GSM модем A6 в режиме TCP.

Logik
Offline
Зарегистрирован: 05.08.2014

В продолжение работы, про которую писал в http://arduino.ru/forum/apparatnye-voprosy/gsm-a6-vopros-pro-ring . Там все закончилось жестоким разочарованием в софт сириале, да и не потеме ринга оно вобще.

В общем TCP я поднял, и добился работы, но есть вопросы. 

Код.

#include "SSD1306.h"


#define OLED_SCL_PIN 6
#define OLED_SDA_PIN 7
ssd1306_i2c(OLED_SCL_PIN, OLED_SDA_PIN, myOLED) ;


typedef void (*pFn)(char* p);
typedef boolean (*pFnb)(char* p);

typedef struct sEventsMap
{
  const char* Name;
  pFn Event;
};



void (*pFnTimer)(void) ;
int PauseTimer;

byte StatModem;

enum
{
  UNKNOWN,
  CONNECT,
  BEGIN_INIT,
  END_INIT
}  STATMODEM ;

/****************************************
  Набор функций общения с экраном и последовательным каналом - пережитком прошлого софтового 
*/
char InpStr[100];
void SerialOut(char* p=InpStr){ /*Serial.println(p);*/}

void ClrScr(){myOLED.clrScr();}
void DravString(byte str, byte row, char *p)
{
   char *a;
   for(a=p;(*a!='\r') && (*a!='\n') && (*a!=0);a++) {}; 
   char r=*a;
   *a=0;
   myOLED.drawString(str,row,p);
   *a=r;
}

//Модем хоть раз сказал ОК - конект есть, запускаем паузу перед инициализацией
void ReciveAT_OK(char* p)
{
  PauseTimer=millis()+2000;
  pFnTimer=BeginInit;
  StatModem=CONNECT;
};

bool ActivAT;
pFn EventOk=ReciveAT_OK;
//базовая обработка ОК. Специализация в функции EventOk
void ReciveOK(char* p){digitalWrite(13,LOW);ActivAT=false; if(EventOk) EventOk(p);  };
pFn EventERROR;
//базовая обработка ошибок. Специализация в функции EventERROR
void ReciveERROR(char* p){digitalWrite(13,LOW); ActivAT=false; if(EventERROR) EventERROR(p);  else SerialOut();};


#define DBG_           
void SendAT(const __FlashStringHelper* at)
{
 digitalWrite(13,HIGH);
#ifdef DBG
 char v[20];
 for(byte i=1;i<20;i++)
 {
  v[i]= pgm_read_byte(((int)at)+i-1);
 }
 v[0]='>';
 v[19]=0;
 myOLED.drawString(0,6,"                     ");
 myOLED.drawString(0,6,v);
 myOLED.drawString(0,7,"                     ");
#endif
 
 ActivAT=true;
 Serial.println(at);
}

void SendAT( PGM_P at)
{
 digitalWrite(13,HIGH);
#ifdef DBG
 myOLED.drawString(0,6,">");
 myOLED.drawStringP(1,6,at);
 myOLED.drawString(0,7,"                     ");
#endif
 
 ActivAT=true;
 for(;char r=pgm_read_word(at);at++)
 {
 Serial.print(r);
 }
 Serial.println();
}


/****************************
  Много обработчиков сообщений модема, их таблица sEventsMap EventsMap[]
*/
void ReciveRING(char* p){  SerialOut();  ClrScr(); DravString(6,2,"R I N G");SendAT(F("AT+CLCC"));};

byte ConnectMode=0;
pFn EventChngReg;
void ChngReg(char* p)
{
  byte r=(*(p+3))-'0';
  if(r!=ConnectMode)
   if(EventChngReg)
   {
     ConnectMode=r;
     EventChngReg(p);
   }
 ConnectMode=r;
}


void ReciveCOPS(char* p)
{
  p++;
  SerialOut(p);
  if((*p)!='(')    //список всех сетей
  { 
   DravString(0,7,p);
   return;
  }
  ClrScr(); 
  byte n=1;
  for(char *a=p, *b=p;*a!=0;a++)
  {
   if((*a==')') && (*(a+1)!='"'))  //life - козлы!!!
   {
     a++;
     *a=0;
     DravString(0,n,b);
     n++;
     b=a+1;
   } 
  }
}
void ReciveSMS(char* p)
{
 //  Serial.println("SMS"); 
   SerialOut();
   
   ClrScr();
   DravString(7,3,"S M S");
   
   SendAT(F("AT+CMGF=1"));
   SendAT(F("AT+CMGR=1"));
 }
void ReadSMS(char* p){ SerialOut();}
void SignalQuality(char* p){ DravString(10,0,"sign:");DravString(15,0,p); SerialOut();}
void ReadICCID(char* p)
{
  char *a=p;
  for(;*a>=' ';a++)
  {
   if(!(((int)a)&3)) *a='*';
  }
  *a=0;
  DravString(0,5,p); 
  SerialOut(p);
}

void EndCALL(char* p){DravString(4,7,p);}

void DetaleRING(char* p)
{ 
  char *a=p;for(;*a!='"';a++);
  *a=0;
  DravString(3,4,p);
  *a='"';*(a+4)='*';*(a+7)='*';*(a+9)='*';*(a+11)='*';
  DravString(1,5,a);
}

void(*RawData)(byte r, word l);

class StreamFiltr
{
  const char * et;
  byte c;
  byte l;
  public:
   StreamFiltr(const char * e) {et=e;l=strlen(et)-1;}
   boolean Find(char r)
   {
    if(r==et[c])
    {
      if(c==l)
      {
        c=0;
        return true;
      }
      c++;
    }
    else
      c=0;
    return false;
   };
   void Reset(void){c=0;}
};

StreamFiltr FiltrCIPRCV("+CIPRCV:");
StreamFiltr FiltrTag_h4("<h4>");
StreamFiltr FiltrTag_A("<a href=");


byte ConvUTF8(byte r)
{
  static byte utf8_1;

  if(r&0x80)  //UTF-8 только двухбайтовая кирилица
  {
     if(r&0x40)
     {
        utf8_1=r;
        return 0;
     }
     else
     {
       r=byte(r&0x3f | byte(utf8_1<<6));
       if(r==0x051) r=0x35; //ё
       if(r==0x001) r=0x15;  //Ё
       r+=0xb0;
    }
  }
  return r;
}

void OutTextScr(char r)
{
  static byte y;
  static byte state;
  static byte cnt;
  static char buf[21];
  
  switch(state)
  {
    case 0:
     FiltrTag_h4.Reset();
     cnt=0;
    case 1:
      if(FiltrTag_h4.Find(r))
      {
        y=0;
        cnt++;
        if(cnt==2)
        {
          state=2;
          ClrScr();
          cnt=0;
        }
      }
      else
        state=1;
      break; 
    case 2:
      if(!(r=ConvUTF8(r)))
        break;
      buf[cnt]=r;
      cnt++;
      if(cnt==20)
      {
        buf[20]=0;
        cnt=0;
        state=3;     
        DravString(0,1,buf);
      }
      
      break;  
    case 3:
      if(FiltrTag_A.Find(r))
        state=4;
      break;
    case 4:
      if(r=='>')
      {
        cnt=0;
        state=5;
      }
      break;
    case 5:
      if(!(r=ConvUTF8(r)))
        break;
      buf[cnt]=r;
      cnt++;
      if(cnt==20)
      {
        buf[20]=0;
        DravString(0,y+3,buf);
   
        y++;
        if(y==5)
        {
         state=1;
         cnt=2;
         y=0;
        }
        else
          state=3;     
      }
      break;  

    
  }
}

void Send1(byte r, word l)
{
  static char buf[8];
  static byte ind;
  static byte ind2;
  static word cnt;

  if(cnt)
  {
    OutTextScr(r);
    cnt--;
    if(!cnt)
    {
     FiltrCIPRCV.Reset();
     ind2=1;
    }
    return;
  }
  
   if(ind)
  {
    if(r==',')
    {
      buf[ind-1]=0;
      if(ind2)
      {
        cnt=atol(buf);
        ind=0;
        ind2=0;
       return;
      }
      ind2++;
      ind=1;
     }
    else
    {
       buf[ind-1]=r;
       if(ind<8)
         ind++;
    } 
    return;
  }
   
   if(ind2)
   {
     
   if(FiltrCIPRCV.Find(r))
     {
      ind=1;
       ind2=0;
       return;
     }
     
   }
 


   if(r=='>')
   {
     ClrScr();
     DravString(6,3,"S E N D");delay(300);
     SendAT(F("GET / HTTP/1.0\r\nHost: arduino.ru\r\n"));
     FiltrCIPRCV.Reset();
     DravString(4,3,"R E C I V E");
   
     ind=0;
     ind2=1;
    }
  
 }

sEventsMap EventsMap[]={
  {"OK",       ReciveOK},
  {"RING",     ReciveRING},
  {" ERROR:",  ReciveERROR},
  {"+CME",     ReciveERROR},
  {"+CIEV:",   EndCALL},
  {"+CREG:",   ChngReg},
  {"+COPS:",   ReciveCOPS},
  {"^CINIT:",  SerialOut},
  {"+CMTI",    ReciveSMS},
  {"+CMGR",    ReadSMS},
  {"+CSQ:",    SignalQuality},
  {"+CCID:",   ReadICCID},
  {"+CLCC:",   DetaleRING},
  
 
  {0,0}  //признак конца списка
};

boolean ReciveATI_OK(char* p)
{
  SerialOut(p);
  ClrScr();
  
  byte n=0;
  char *b=p;
  char *a;
  byte StrEnd=0;
  
  for(a=p;*a!=0;a++)
  {
   if(*a==10) StrEnd|=1;
     else if(*a==13) StrEnd|=2;
       else
        StrEnd=0;
 
   if(StrEnd==3)
   {
     DravString(0,n,b);
     b=a+1;
     n++;
   }
  }
  DravString(0,n,b);

  DravString(0,4,"Маскированый ICCID:");
  return true;
};


boolean ReciveAT_CREG_OK(char* p)
{
  if(ConnectMode!=1)
  {
     ReciveInit_ERROR(p);
     return false; //повторим эту команду
  }
  EventChngReg=ReciveInit_ERROR;  //т.к. не стартует при незавершенной регистрации то перезапустим по её завершению
  DravString(5,7,"Поиск сетей...");
  return true;
};

boolean ReciveAT_QSQ_OK(char* p){  SerialOut();return true;};

boolean ReciveAT_GATT(char* p){ ClrScr(); return true;};
boolean LockalIP(char* p){ DravString(0,1,"IP:");DravString(3,1,p); SerialOut();delay(3000);return true;}

typedef struct AT_COMMANDS
{
  PGM_P at;
  pFnb  Fn;
  pFn   Err;
} ;

const char ATE[] PROGMEM     ="ate 0";
const char ATI[] PROGMEM     ="ati";
const char ATCCID[] PROGMEM  ="at+ccid";
const char ATCREG[] PROGMEM  ="at+creg?";
const char ATCOPS[] PROGMEM  ="at+cops=?";
const char ATCOPSC[] PROGMEM ="at+cops?";
const char ATCSQ[] PROGMEM   ="at+csq";

const char ATCGATT[] PROGMEM    ="AT+CGATT=1";
const char ATCIPMUX[] PROGMEM   ="AT+CIPMUX=1";
const char ATCGDCONT[] PROGMEM  ="AT+CGDCONT=1,\"IP\",\"www.ab.kyivstar.net\"";
const char ATCSTT[] PROGMEM     ="AT+CSTT=\"www.ab.kyivstar.net\",\"\",\"\"";
const char ATCIICR[] PROGMEM    ="AT+CIICR";
const char ATCIFSR[] PROGMEM    ="AT+CIFSR";




 AT_COMMANDS AT_Command[]=
{
  {ATE,      NULL, NULL},
  {ATI,      ReciveATI_OK, NULL},  
  {ATCCID,   NULL, NULL},  
  {ATCREG,   ReciveAT_CREG_OK, NULL},  
  {ATCOPS,   NULL, NULL},  
  {ATCOPSC,  NULL, NULL},  
  {ATCSQ,     ReciveAT_QSQ_OK, NULL},  
  
  {ATCGATT,  ReciveAT_GATT, NULL },
  {ATCSQ,    ReciveAT_QSQ_OK, NULL},  
  {ATCIPMUX,  NULL, NULL },
  {ATCGDCONT,  NULL, NULL },
  {ATCSTT,  NULL, NULL },
  {ATCIICR,  NULL, NULL },
  {ATCGATT,  NULL, NULL },
  {ATCIFSR,  LockalIP, NULL },
  
  
  
};


byte InitID; 
void SendCmd(void){ SendAT(AT_Command[InitID].at); }
void BeginInit()
{ 
  StatModem=BEGIN_INIT;
  EventOk=ReciveInit_OK;

  InitID=0;
  SendCmd(); 
}

void ReciveInit_OK(char* p)
{
  if(InitID<sizeof(AT_Command)/sizeof(AT_Command[0]))
  {
   EventChngReg=0;
   if(AT_Command[InitID].Err)
     EventERROR=AT_Command[InitID].Err;
   else
     EventERROR=ReciveInit_ERROR; 

   if(AT_Command[InitID].Fn)
   {
    if(!AT_Command[InitID].Fn(p))
      return;  
   }
   InitID++;
   if(InitID<sizeof(AT_Command)/sizeof(AT_Command[0]))
     SendCmd();
   else
   {
     EventOk=SerialOut;  
     StatModem=END_INIT;
   }
  }
  
};

void ReciveInit_ERROR(char* p)
{
  EventChngReg=0;
  AT_Command[InitID-1].Fn(p);
}

pFn OK_Fn=ReciveAT_OK;



void TetraPack(byte *p, byte len, char *d) 
{
 for(byte i=0;i<len;i++,p++)
 {
    *d=0x30|*p&0x0f;d++;
    *d=0x30|*p>>4;d++;
 }
}

byte NumberServPack(byte *p, char *d){  byte l=(*p)-1;  TetraPack(p+2, l, d);  d[l<<1]=0;return l+2;}
byte NumberPack(byte *p, char *d){  byte l=(*p)+1>>1;  TetraPack(p+2, l, d);  d[*p]=0;return l+2;}
byte TimerPack(byte *p, char *d){  TetraPack(p, 7, d); d[14]=0;return 7;}
byte TextPack(byte *p, char *dc)
{
  word b=0;
  byte k=0;
  byte i=0;
  byte* d=(byte*)dc;
  byte c=*p;
  p+=1;
  for(;c;c--)
  {
    if(!i)
    {
      b=*p++;
      i=7; 
    }
    Serial.print(b,HEX);Serial.print("  ");
    b|=(*p)<<i;p++;
    Serial.print(b,HEX);Serial.print("  ");
    *d=(byte)b&0xff;
    Serial.println(*d,HEX);
    d++;
    k++;
    b>>=8;
    i--;
  }
  *d=0;
  return k+1;
  
}

const byte sms[]=
{0x07,0x91,0x83,0x90,0x01,0x44,0x60,0x10,
 0x04,
 0x0C,0x91,0x83,0x90,0x91,0x29,0x98,0x78,
 0x00,
 0x08,
 0x71,0x40,0x02,0x91,0x70,0x24,0x00,
 0x44, 
 0x30, 0x02, 0x00, 0x07, 0x47, 0x40, 0x02, 0x00,    
 0x06, 0x10, 0x06, 0x10, 0x06, 0x10, 0x06, 0x20,0x06};
 
void setup() 
{
  Serial.begin(9600);
//  mySerial.begin(9600);
  pinMode(13, OUTPUT);
  

  myOLED.begin();
  myOLED.clrScr();
  myOLED.setFont( SSD1306::FONT_SIZE_2); 
  myOLED.drawString(1,1,"Пейджер");
  myOLED.drawString(0,3,"ардуинщика");
  myOLED.setFont( SSD1306::FONT_SIZE_1);
  delay(1000);
  /*
  char txt[100];
  byte *r=(byte*)sms;
  r+=NumberServPack(r,txt);
  Serial.println(txt);
  r++;  //не знаю что оно такое  04h  TP-MTI   First octet of this SMS-DELIVER message.
  r+=NumberPack(r,txt);Serial.println(txt);
  r++;  //не знаю что оно такое TP-PID  00h 
  r++;  //не знаю что оно такое TP-DCS Data coding scheme 08h UCS2
  r+=TimerPack(r,txt);Serial.println(txt);
  r+=TextPack(r,txt);Serial.println(txt);
  delay(2000);
*/
};

boolean ExecAT(char* pInpStr, byte currchar, sEventsMap *em);
boolean ExecAT(char* pInpStr, byte currchar, sEventsMap *em)
{
      sEventsMap *e=em;

      for(;e->Name;e++)
      {
        if(!memcmp(e->Name,pInpStr+currchar,strlen(e->Name)))
        {
         if(e->Event)
         {
           char *p=InpStr;
           if(e==em)
             pInpStr[currchar]=0; //если ОК - удаляем его
           else
             p+=strlen(e->Name); 
  
            e->Event(p);
         }
         return true;
       }
      }
  return false;  
}

   
  
void ProcessAT(byte r, sEventsMap *e);  //wiring - dibiling
/* процесс посимвольного приема данных с модема, 
формирует непустые строки и вызывает разбор команды из sEventsMap *e*/
void ProcessAT(byte r, sEventsMap *e)
  {
    static byte StrEnd;
    static char* pInpStr=InpStr;
    static char* pNewStr=InpStr;
    
    if(RawData)
    {
     RawData(r, 1000);
     return; 
    }
    if(r==10) StrEnd|=1;
     else if(r==13) StrEnd|=2;
       else
        StrEnd=0;

    *pInpStr=r;
    if(pInpStr<(InpStr+sizeof(InpStr)-1))
      pInpStr++;

    if(StrEnd==3)
    {
      if((pInpStr-pNewStr)==2)
      {
        StrEnd=0;
        pInpStr=pNewStr;

      return;
      }

     *pInpStr=0;
#ifdef DBG
   DravString(0,7,"<                    ");
   DravString(1,7,pNewStr);
 // delay(10000);
#endif   
      if(ExecAT(InpStr, pNewStr-InpStr, e)) //ищем и исполняем команду, если успешно то удалим из буфера
      {
        pInpStr=InpStr;
        StrEnd=0;
      }
      pNewStr=pInpStr;
    }   
  }


int ww;
void loop() {
  int T=millis();

  for(;Serial.available();)  //выгребаем все что принято, парсим и исполняем согласно 
  {
    ProcessAT(Serial.read(), EventsMap);
  }

  
  if(StatModem==UNKNOWN)
  {
    SendAT(F("AT"));
    digitalWrite(13,ww&1);
    ww++;
    delay(ww%200?24:200);
    
    return;
  }


  if(pFnTimer)
  {
    if(T-PauseTimer>0)
    {
     pFnTimer();
     pFnTimer=NULL;
    }
  }
  

  
  static int TimeCSQ;
  if(ActivAT || (StatModem!=END_INIT))
   TimeCSQ=T;
  else if(T-TimeCSQ>1000)
  {
    static byte AT_state;
   TimeCSQ=T;
    switch(AT_state)
    {
     case 1:    SendAT(F("AT+CIPSTART=\"TCP\",\"78.46.72.113\",80"));break;
     case 2:     SendAT(F("AT+CIPSEND=0,34"));
                 RawData=Send1;
                  break;
    case 17:     SendAT(F("AT+CIPSHUT"));break;
    case 18:     SendAT(F("at+csq"));break;
   
    }
    if(AT_state<18)
     AT_state++;

  }

}

Работает, выводит

Теперь на железном сириале. Подключен так Tx наны на RX модуля, RX наны на анод (ну плюс ;) диода а его анод к Tx модема. Электролит на питании.

Теперь вопросы.

1. Люди добрые, христа ради, подайте ссылку на даташит к модему. Только поновей, у меня есть v1.01 (с ней прошу не беспокоить),  но в версии 3 сильно не так.

2. Кто сечет в АТ командах GPRS и ТСР. Я делаю соединение так перед отправкой запроса серверу:

"AT+CGATT=1";

"AT+CIPMUX=1";

"AT+CGDCONT=1,\"IP\",\"www.ab.kyivstar.net\"";
 
"AT+CSTT=\"www.ab.kyivstar.net\",\"\",\"\"";
 
"AT+CIICR";
 
"AT+CIFSR";
Оно работает, но я не сильно понимаю чего я делаю )) Прокоментируйте вобщем, кто шарит .
 
 
3. Непосредственно по обмену. Непосредственно запрос так "AT+CIPSEND=0,34", где 34 - длина будущих данных, затем жду ">" после чего собственно HTTP запрос SendAT(F("GET / HTTP/1.0\r\nHost: arduino.ru\r\n"));
Начинаю получать ответы вида "+CIPRCV: 0,1400ХХХХХ..." где ХХХ-данные числом 1400. Как узнать, когда принято все? Я так понимаю, что в последнем таком прийдет не 1400 байт, а меньше (на это можна положится?), а если передали кол-во кратное 1400, то как быть?

 

Logik
Offline
Зарегистрирован: 05.08.2014

В коде, который я к сожалению забыл под спойлер свернуть, остались следы приема входящего звонка и попыток работы с SMS. Состояние этого функционала неизвестно, когдато работало, а сейчас симка стала чисто TCP (происки оператора))) и не проверялось.

Поэксперементировал со скоростю обмена. Можна смело заменить 9600 на 38400, успевает. А вот 57600 уже нет. 

alex_r61
Offline
Зарегистрирован: 20.06.2012

Новее январской версии доков не видел. Здесь не смотрели http://m2msupport.net/m2msupport/at-command/

И тут ещё видео, правда на вражеском..:) https://www.youtube.com/watch?v=qtzrVj66IOA

Logik
Offline
Зарегистрирован: 05.08.2014

alex_r61 пишет:

Здесь не смотрели http://m2msupport.net/m2msupport/at-command/

Спасибо, глянул на http://m2msupport.net/m2msupport/at-command/ . Безрадостно. Там какаято софтина - АТ тестер, а в описании команд при ней например сказано

Send data

AT+CIPSEND

А наш А6 такое не понимает, понимает "AT+CIPSEND=0, 123", если не с 2-я параметрами то ругается. С вторым параметром все ясно, по аналогии с другими модулями это длина передаваемых далее данных (хотя не все ясно, какое максимальное значение не известно..). А первый не известен, возможно какойто идентификатор соединения или сессии или ХЗ. Но ноль проходит - вот и ладно. В общем без даташита конкретно на А6 тяжко.

Logik
Offline
Зарегистрирован: 05.08.2014

Еще проблемка. Дисконектится корректно не выходит. По доке это

The command disconnects the wireless connection, except at the status of IP INITIAL. You can close moving scene by AT+CIPSHUT.

В реале после SendAT(F("AT+CIPSHUT=1")); вылазит ERROR:53. По доке это:

53 PARAM_INVALID.

И в остальных случаях когда я сталкивался с ней это так и означало.

Пробую SendAT(F("AT+CIPSHUT=0")); или SendAT(F("AT+CIPSHUT=1")) вылазит ERROR:50 , что по доке вроде как  EXE_FAIL.

Тупик.

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

ПС. Команда AT+CIPCLOSE ведет ся аналогично.

Logik
Offline
Зарегистрирован: 05.08.2014

Возможно соединение закрывается автоматом после выполнения AT+CIPSEND. Поскольку второй сенд не проходит так

     SendAT(F("AT+CIPSTART=\"TCP\",\"78.46.72.113\",80"));
     SendTCP(http_get, http_recive); //AT+CIPSEND
     SendAT(F("at+csq"));    //для проверки прохождения команд в принципе
     SendTCP(http_get, http_recive);  //AT+CIPSEND

 

 

а если вставить еще один AT+CIPSTART, перед вторым сендом то проходит все корректно.

Skarm
Offline
Зарегистрирован: 11.05.2017

На счёт свежих даташитов глянь тут http://wiki.ai-thinker.com/gprs , сам там смотрю , но так как с планшета и лимитированным интернетом. Там правда сайт на китайском, но когда нас останавливали это☺. Нашёл версию 1.03_1, нужна?? Это последняя на оф сайте

 

Skarm
Offline
Зарегистрирован: 11.05.2017

На третий вопрос из первого поста любой ответ от модема должен заканчиваться переводом строки "<CR><LF><response><CR><LF>".

Logik
Offline
Зарегистрирован: 05.08.2014

А что есть <response>? наверно ОК или код ошибки..

Исходя из этого, получается если мне ответили 1400 байт и закрыли канал, то получу

"+CIPRCV: 0,1400ХХХХХ..."

"<CR><LF><response><CR><LF>".

 

Если  ответили 1400 байт и не закрыли канал, то получу просто

"+CIPRCV: 0,1400ХХХХХ..."

Потом могут и чего дослать. Получу следующие "+CIPRCV:

 

Если  ответили ответили 1401 байт и закрыли канал, то получу

"+CIPRCV: 0,1400ХХХХХ..."

"+CIPRCV: 0,1ХХХХХ..."

"<CR><LF><response><CR><LF>".

Так шоле?

ПС. Спасибо за ссылку, качнул, теперь ищу чем 7z разархивировать )))

Skarm
Offline
Зарегистрирован: 11.05.2017

Logik ты ответ в терминале смотришь, на компьютере???? Терминал не отображает эти символы

https://ru.m.wikipedia.org/wiki/Перевод_строки.

response - это сообщение которое отправляет модем.

Если соединить модем и контроллер то эти символы приходят на usart и по ним можно судить о начале и завершении передачи данных.

Если в шеснацитиричной системе рассматривать ответ от модема то он будет выглядеть так "0х0d 0x0a (данные от модема в том числе и+CIPRCV: 0,1400ХХХХХ  только в шеснацетиричншеснацетиричном исчеслении ) 0х0d 0x0a."

для распаковки 7z  тут http://www.7-zip.org

 

Logik
Offline
Зарегистрирован: 05.08.2014

Я ответ все больше на ардуине смотрю, то что <CR><LF> - в си десятичное 13, 10 (\r\n или 0х0d 0x0a), это как бы и не обсуждается.

Речь вобще не о ответе модема при send-е, т.е. на "AT+CIPSEND. Чего я описал как "Непосредственно запрос так "AT+CIPSEND=0,34", где 34 - длина будущих данных, затем жду ">" после чего собственно... " Про <CR><LF> я опустил, как малозначимое. Там вобще вопросов нет. Но есть расхождение между "вашим и нашим", я жду  ">" оно точно есть и после него только даввные отправляются. Еще - эхо я отключил, так замечу, для определенности ;)

И +CIPRCV: 0,1400ХХХХХ - в шестнадцетеричном только ХХХХХ, думаю и Вы это имели в виду. 

Вопрос в другом - конец ответа (оно же - закрытие канала отвечающей стороной) как себя обозначает?

 

 

Logik
Offline
Зарегистрирован: 05.08.2014

Глянул описание АТ версии 1.03. Оно Немного лучше. Гдето на таком уровне.

В старом было

10.2.2 Syntax Test command AT+ CIPSEND =? Response(s) Success: OK Execution command AT+ CIPSEND Description Response ”>”, then type data for send, tap CTRL+Z to send. Response(s) Success: OK Fail: ERROR Reference
10.2.3 Unsolicited Result Codes

В новом добавили к старому еще

使用例子 如下:
AT+CIPSEND=5,”12345” //同步发送字符串
AT+CIPSEND=5 //出现”>”后可以发送5个字节的二进制数据
AT+CIPSEND //出现”>”后可以发送以CTRL+Z结尾的字符串
 
А работает у меня так 
AT+CIPSEND=0,5,”12345”
Причем ”12345” передавать только после ответа от модема ">"
В общем новый лучше, но не сильно ))) 
 
Родилась мысль что ресивы работают так
"+CIPRCV: 0,1400ХХХХХ..."
"+CIPRCV: 0,123ХХХХХ..."
"+CIPRCV: 0,1400ХХХХХ..."
"+CIPRCV: 0,500ХХХХХ..."
"+CIPCCLOSE: 0."
 
 
Т.е. После открытия канала, даже без сенда, могут асинхронно валится любые варианты любых длин данных (до 1400 разумеется) в сообщениях "+CIPRCV:N,L..." где N-номер канала указаный при открытии (может и несколько быть!!!) L-длина прилетевшего блока данных. Между ними можна выполнять любые АТ команды, сендить в том числе. При закрытии канала с противоположной стороны прийдет некий "+CIPCCLOSE: 0." - я его сам придумал!!! После него уже на данный канал ниче не прийдет. Надо проверить и установить синтаксис "+CIPCCLOSE: 0." если оно есть (позже установлено оно есть, но выглядит как +TCPCLOSED:0). 
 
 
Skarm
Offline
Зарегистрирован: 11.05.2017

Logik скинь на мыло skarm@mail.com новый даташит, а то с планшета не получаетьс скачать

 

Logik
Offline
Зарегистрирован: 05.08.2014

отправил все архивы что скачал и вытянутое и переименованое из них описание команд a6_at_commands_V1.03.pdf Там в архивах есть любопытное, например сервер на пыхе для приема фоток с А6С. Я так мельком глянул, так вроде оно.

Logik
Offline
Зарегистрирован: 05.08.2014

Залогировал ресив. На запрос сайта имею следующий ответ, его много, потому парежу и заменю ......

 

+CIPRCV:0,1400,HTTP/1.1 200 OK Server: nginx/1.2.5 Date: Sat, 13 May 2017 18:58:21 GMT ........................

^Z+CIPRCV:0,1400,ttps://*.google.com; report-uri /csp2.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" > ..........................

^Z+CIPRCV:0,1400,оÑиÑÑ Ð½Ð¾Ð²Ñй паÑÐ¾Ð»Ñ Ð¿Ð¾ ÑлекÑÑонной поÑÑе.">ÐабÑли паÑолÑ?</a></li> </ul></div> .........................

^Z+CIPRCV:0,433,;

        // -->
    </script></strong></div>
  </div>
 
</div>
        <a href="http://arduino.ru/rss.xml" class="feed-icon"><img src="/misc/feed.png" alt="RSS-маÑеÑиал" title="ÐппаÑаÑÐ½Ð°Ñ Ð¿Ð»Ð°ÑÑоÑма Arduino RSS" width="16" height="16" /></a>      </div> <!-- /#footer -->
  <script type="text/javascript" src="/sites/default/files/js/js_31d393bded9080907e35f196f52c3321.js"></script>
 
  </body>
</html>
 
^Z

+TCPCLOSED:0

OK

 

Жирным выделил то, что к протоколу обмена относится. В отношении ^Z не уверен, в мониторе квадратик, который при копипасте воспринимается как конец текста, на 99% оно. Но полагатся на него нельзя, поток данных вполне может быть и бинарный, тогда там этих символов валом будет.

В общем закрытие канала и завершение приема соответственно надо ловить по +TCPCLOSED:0.

ПС. Шоле китайцам доку написать о их модеме )))))

 

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

давай на державной мове? чтоб китайцы учились по европейски читать. :)

Logik
Offline
Зарегистрирован: 05.08.2014

Державная на форуме - виринг! Диалект Си ;)

Logik
Offline
Зарегистрирован: 05.08.2014

Logik пишет:

 В отношении ^Z не уверен, в мониторе квадратик, который при копипасте воспринимается как конец текста, на 99% оно.

 

Не зря я себе процентик приберег, он и перевесил )))

Процесс завершения одного потока данных и начала следующего 

0x68, 0x0d, 0x0a 0x0d, 0x0a, 0x00, "+CIPRCV:0,1400," , 0x74,....

где 0x68 - последний байт первого пакета в соответствии с ранее указаной длиной

0x74 - первый байт следующего.

как видим 0x0d, 0x0a 0x0d, 0x0a, 0x00, отсебячина модема, а "+CIPRCV:0,1400," вобщем понятно, выше расписывал.

Logik
Offline
Зарегистрирован: 05.08.2014

Доработал код с учетом новых знаний.

#include "SSD1306.h"


#define OLED_SCL_PIN 6
#define OLED_SDA_PIN 7
ssd1306_i2c(OLED_SCL_PIN, OLED_SDA_PIN, myOLED) ;


typedef void (*pFn)(char* p);
typedef boolean (*pFnb)(char* p);

typedef struct sEventsMap
{
  const char* Name;
  pFn Event;
};



void (*pFnTimer)(void) ;
int PauseTimer;

byte StatModem;

enum
{
  UNKNOWN,
  CONNECT,
  BEGIN_INIT,
  END_INIT
}  STATMODEM ;

/****************************************
  Набор функций общения с экраном и последовательным каналом - пережитком прошлого софтового 
*/
char InpStr[100];
void SerialOut(char* p=InpStr){ /*Serial.println(p);*/}

void ClrScr(){myOLED.clrScr();}

void DravString(byte str, byte row, char *p)
{
   char *a;
   for(a=p;(*a!='\r') && (*a!='\n') && (*a!=0);a++) {}; 
   char r=*a;
   *a=0;
   myOLED.drawString(str,row,p);
   *a=r;
}

//Модем хоть раз сказал ОК - конект есть, запускаем паузу перед инициализацией
void ReciveAT_OK(char* p)
{
  PauseTimer=millis()+3000;
  pFnTimer=BeginInit;
  StatModem=CONNECT;
};

bool ActivAT;
pFn EventOk=ReciveAT_OK;
//базовая обработка ОК. Специализация в функции EventOk
void ReciveOK(char* p){digitalWrite(13,LOW);ActivAT=false; if(EventOk) EventOk(p);  };
pFn EventERROR;
//базовая обработка ошибок. Специализация в функции EventERROR
void ReciveERROR(char* p){digitalWrite(13,LOW); ActivAT=false; if(EventERROR) EventERROR(p);  else SerialOut();};


#define DBG_           
void SendAT( char* at)
{
   digitalWrite(13,HIGH);
#ifdef DBG
 myOLED.drawString(0,6,">                  !!");
 //myOLED.drawString(1,6,at);
 myOLED.drawString(0,7,"                     ");
#endif  
  ActivAT=true;
  Serial.println(at); 
}

void SendAT(const __FlashStringHelper* at)
{
 digitalWrite(13,HIGH);
#ifdef DBG
 char v[20];
 for(byte i=1;i<20;i++)
 {
  v[i]= pgm_read_byte(((int)at)+i-1);
 }
 v[0]='>';
 v[19]=0;
 myOLED.drawString(0,6,"                     ");
 myOLED.drawString(0,6,v);
 myOLED.drawString(0,7,"                     ");
 //delay(3000);
#endif
 
 ActivAT=true;
 Serial.println(at);
}

void SendAT( PGM_P at)
{
 digitalWrite(13,HIGH);
#ifdef DBG
 myOLED.drawString(0,6,">                   -");
 myOLED.drawStringP(1,6,at);
 myOLED.drawString(0,7,"                     ");
// delay(3000);
#endif
 
 ActivAT=true;
 for(;char r=pgm_read_word(at);at++)
 {
   Serial.print(r);
 }
 Serial.println();
}


/****************************
  Много обработчиков сообщений модема, их таблица sEventsMap EventsMap[]
*/
void ReciveRING(char* p){  SerialOut();  ClrScr(); DravString(6,2,"R I N G");SendAT(F("AT+CLCC"));};

byte ConnectMode=0;
pFn EventChngReg;
void ChngReg(char* p)
{
  byte r=(*(p+3))-'0';
  if(r!=ConnectMode)
   if(EventChngReg)
   {
     ConnectMode=r;
     EventChngReg(p);
   }
 ConnectMode=r;
}


void ReciveCOPS(char* p)
{
  p++;
  SerialOut(p);
  if((*p)!='(')    //список всех сетей
  { 
   DravString(0,7,p);
   return;
  }
  ClrScr(); 
  byte n=1;
  for(char *a=p, *b=p;*a!=0;a++)
  {
   if((*a==')') && (*(a+1)!='"'))  //life - козлы!!!
   {
     a++;
     *a=0;
     DravString(0,n,b);
     n++;
     b=a+1;
   } 
  }
}
void ReciveSMS(char* p)
{
   SerialOut();
   
   ClrScr();
   DravString(7,3,"S M S");
   
   SendAT(F("AT+CMGF=1"));
   SendAT(F("AT+CMGR=1"));
 }
void ReadSMS(char* p){ SerialOut();}
void SignalQuality(char* p){ DravString(10,0,"sign:");DravString(15,0,p); SerialOut();}
void ReadICCID(char* p)
{
  char *a=p;
  for(;*a>=' ';a++)
  {
   if(!(((int)a)&3)) *a='*';
  }
  *a=0;
  DravString(0,5,p); 
  SerialOut(p);
}

void EndCALL(char* p){DravString(4,7,p);}

void DetaleRING(char* p)
{ 
  char *a=p;for(;*a!='"';a++);
  *a=0;
  DravString(3,4,p);
  *a='"';*(a+4)='*';*(a+7)='*';*(a+9)='*';*(a+11)='*';
  DravString(1,5,a);
}



class StreamFiltr
{
  const char * et;
  byte c;
  byte l;
  public:
   StreamFiltr(const char * e) {et=e;l=strlen(et)-1;}
   boolean Find(char r)
   {
    if(r==et[c])
    {
      if(c==l)
      {
        c=0;
        return true;
      }
      c++;
    }
    else
      c=0;
    return false;
   };
   void Reset(void){c=0;}
};

StreamFiltr FiltrCIPRCV("+CIPRCV:");
StreamFiltr FiltrTag_h4("<h4>");
StreamFiltr FiltrTag_A("<a href=");


byte ConvUTF8(byte r)
{
  static byte utf8_1;

  if(r&0x80)  //UTF-8 только двухбайтовая кирилица
  {
     if(r&0x40)
     {
        utf8_1=r;
        return 0;
     }
     else
     {
       r=byte(r&0x3f | byte(utf8_1<<6));
       if(r==0x051) r=0x35; //ё
       if(r==0x001) r=0x15;  //Ё
       r+=0xb0;
    }
  }
  return r;
}



sEventsMap EventsMap[]={
  {"OK",       ReciveOK},
  {"RING",     ReciveRING},
  {" ERROR:",  ReciveERROR},
  {"+CME",     ReciveERROR},
  {"+CIEV:",   EndCALL},
  {"+CREG:",   ChngReg},
  {"+COPS:",   ReciveCOPS},
  {"^CINIT:",  SerialOut},
  {"+CMTI",    ReciveSMS},
  {"+CMGR",    ReadSMS},
  {"+CSQ:",    SignalQuality},
  {"+CCID:",   ReadICCID},
  {"+CLCC:",   DetaleRING},
  {"+TCPCLOSED:",  CloseTCP},
  
  {0,0}  //признак конца списка
};

boolean ReciveATI_OK(char* p)
{
  SerialOut(p);
  ClrScr();
  
  byte n=0;
  char *b=p;
  char *a;
  byte StrEnd=0;
  
  for(a=p;*a!=0;a++)
  {
   if(*a==10) StrEnd|=1;
     else if(*a==13) StrEnd|=2;
       else
        StrEnd=0;
 
   if(StrEnd==3)
   {
     DravString(0,n,b);
     b=a+1;
     n++;
   }
  }
  DravString(0,n,b);

  DravString(0,4,"Маскированый ICCID:");
  return true;
};


boolean ReciveAT_CREG_OK(char* p)
{
  if(ConnectMode!=1)
  {
     ReciveInit_ERROR(p);
     return false; //повторим эту команду
  }
  EventChngReg=ReciveInit_ERROR;  //т.к. не стартует при незавершенной регистрации то перезапустим по её завершению
  DravString(5,7,"Поиск сетей...");
  return true;
};

boolean ReciveAT_QSQ_OK(char* p){  SerialOut();return true;};

boolean ReciveAT_GATT(char* p){ ClrScr(); return true;};
boolean LockalIP(char* p){ DravString(0,1,"IP:");DravString(3,1,p); SerialOut();return true;}

typedef struct AT_COMMANDS
{
  PGM_P at;
  pFnb  Fn;
  pFn   Err;
} ;

const char ATE[] PROGMEM     ="ate 0";
const char ATI[] PROGMEM     ="ati";
const char ATCCID[] PROGMEM  ="at+ccid";
const char ATCREG[] PROGMEM  ="at+creg?";
const char ATCOPS[] PROGMEM  ="at+cops=?";
const char ATCOPSC[] PROGMEM ="at+cops?";
const char ATCSQ[] PROGMEM   ="at+csq";

const char ATCGATT[] PROGMEM    ="AT+CGATT=1";
const char ATCIPMUX[] PROGMEM   ="AT+CIPMUX=1";
const char ATCGDCONT[] PROGMEM  ="AT+CGDCONT=1,\"IP\",\"www.ab.kyivstar.net\"";
const char ATCSTT[] PROGMEM     ="AT+CSTT=\"www.ab.kyivstar.net\",\"\",\"\"";
const char ATCIICR[] PROGMEM    ="AT+CIICR";
const char ATCIFSR[] PROGMEM    ="AT+CIFSR";




 AT_COMMANDS AT_Command[]=
{
  {ATE,      NULL, NULL},
  {ATI,      ReciveATI_OK, NULL},  
  {ATCCID,   NULL, NULL},  
  {ATCREG,   ReciveAT_CREG_OK, NULL},  
  {ATCOPS,   NULL, NULL},  
  {ATCOPSC,  NULL, NULL},  
  {ATCSQ,     ReciveAT_QSQ_OK, NULL},  
  
  {ATCGATT,  ReciveAT_GATT, NULL },
  {ATCSQ,    ReciveAT_QSQ_OK, NULL},  
  {ATCIPMUX,  NULL, NULL },
  {ATCGDCONT,  NULL, NULL },
  {ATCSTT,  NULL, NULL },
  {ATCIICR,  NULL, NULL },
  {ATCGATT,  NULL, NULL },
  {ATCIFSR,  LockalIP, NULL },
  
  
  
};


byte InitID; 
void SendCmd(void){ SendAT(AT_Command[InitID].at); }
void BeginInit()
{ 
  StatModem=BEGIN_INIT;
  EventOk=ReciveInit_OK;

  InitID=0;
  SendCmd(); 
}

void ReciveInit_OK(char* p)
{
  if(InitID<sizeof(AT_Command)/sizeof(AT_Command[0]))
  {
   EventChngReg=0;
   if(AT_Command[InitID].Err)
     EventERROR=AT_Command[InitID].Err;
   else
     EventERROR=ReciveInit_ERROR; 

   if(AT_Command[InitID].Fn)
   {
    if(!AT_Command[InitID].Fn(p))
      return;  
   }
   InitID++;
   if(InitID<sizeof(AT_Command)/sizeof(AT_Command[0]))
     SendCmd();
   else
   {
     EventOk=SerialOut;  
     StatModem=END_INIT;
   }
  }
  
};

void ReciveInit_ERROR(char* p)
{
  delay(500);
  EventChngReg=0;
  SendCmd();
}


void TetraPack(byte *p, byte len, char *d) 
{
 for(byte i=0;i<len;i++,p++)
 {
    *d=0x30|*p&0x0f;d++;
    *d=0x30|*p>>4;d++;
 }
}

byte NumberServPack(byte *p, char *d){  byte l=(*p)-1;  TetraPack(p+2, l, d);  d[l<<1]=0;return l+2;}
byte NumberPack(byte *p, char *d){  byte l=(*p)+1>>1;  TetraPack(p+2, l, d);  d[*p]=0;return l+2;}
byte TimerPack(byte *p, char *d){  TetraPack(p, 7, d); d[14]=0;return 7;}
byte TextPack(byte *p, char *dc)
{
  word b=0;
  byte k=0;
  byte i=0;
  byte* d=(byte*)dc;
  byte c=*p;
  p+=1;
  for(;c;c--)
  {
    if(!i)
    {
      b=*p++;
      i=7; 
    }
    Serial.print(b,HEX);Serial.print("  ");
    b|=(*p)<<i;p++;
    Serial.print(b,HEX);Serial.print("  ");
    *d=(byte)b&0xff;
    Serial.println(*d,HEX);
    d++;
    k++;
    b>>=8;
    i--;
  }
  *d=0;
  return k+1;
  
}

const byte sms[]=
{0x07,0x91,0x83,0x90,0x01,0x44,0x60,0x10,
 0x04,
 0x0C,0x91,0x83,0x90,0x91,0x29,0x98,0x78,
 0x00,
 0x08,
 0x71,0x40,0x02,0x91,0x70,0x24,0x00,
 0x44, 
 0x30, 0x02, 0x00, 0x07, 0x47, 0x40, 0x02, 0x00,    
 0x06, 0x10, 0x06, 0x10, 0x06, 0x10, 0x06, 0x20,0x06};
 
void setup() 
{

  Serial.begin(38400);
 // Serial.begin(9600);
  
  pinMode(13, OUTPUT);
  

  myOLED.begin();
  myOLED.clrScr();
  myOLED.setFont( SSD1306::FONT_SIZE_2); 
  myOLED.drawString(1,1,"Пейджер");
  myOLED.drawString(0,3,"ардуинщика");
  myOLED.setFont( SSD1306::FONT_SIZE_1);
  delay(1000);
  /*
  char txt[100];
  byte *r=(byte*)sms;
  r+=NumberServPack(r,txt);
  Serial.println(txt);
  r++;  //не знаю что оно такое  04h  TP-MTI   First octet of this SMS-DELIVER message.
  r+=NumberPack(r,txt);Serial.println(txt);
  r++;  //не знаю что оно такое TP-PID  00h 
  r++;  //не знаю что оно такое TP-DCS Data coding scheme 08h UCS2
  r+=TimerPack(r,txt);Serial.println(txt);
  r+=TextPack(r,txt);Serial.println(txt);
  delay(2000);
*/
};
//**************************************************************

boolean ExecAT(char* pInpStr, byte currchar, sEventsMap *em);
boolean ExecAT(char* pInpStr, byte currchar, sEventsMap *em)
{
      sEventsMap *e=em;

      for(;e->Name;e++)
      {
        if(!memcmp(e->Name,pInpStr+currchar,strlen(e->Name)))
        {
         if(e->Event)
         {
           char *p=InpStr;
           if(e==em)
             pInpStr[currchar]=0; //если ОК - удаляем его
           else
             p+=strlen(e->Name); 
  
            e->Event(p);
         }
         return true;
       }
      }
  return false;  
}

   

//ловим \r\n
boolean TestStrEnd(byte r)
{
  static byte StrEnd;
  
  if(StrEnd==3)
    StrEnd=0; 
  if(r==10) StrEnd|=1;
   else if(r==13) StrEnd|=2;
    else
      StrEnd=0;
  return StrEnd==3?true:false;    
}


void(*RawData)(byte r, void* ctx);
void (*pFnRcv)(byte r, boolean FlBegin);
const char* (*pFnSend)(boolean FlBegin);
void CloseTCP(char* p)
{
 pFnRcv=NULL;
 pFnSend=NULL;
}
 byte FlagsProcessRcvTCP;
 //обработчик для приема данных по TCP, определяет номер канала и размер блока, отправляет данные в соответствующий обработчик
void ProcessRcvTCP(byte r, void *p)
{
  static unsigned int cnt;
  static unsigned int Nch;
  
  switch(cnt)
  {
    case 0:if(RcvInt(r,&Nch)) cnt--;break;
    case 0xffff:RcvInt(r,&cnt);break; 
    case 0xfffe:
       if(!r)
       {
         RawData=NULL;
         cnt=0;
       }
       break;
    default: 
      if(pFnRcv)                //вызов обработчика канала TCP
        pFnRcv(r, FlagsProcessRcvTCP);
      FlagsProcessRcvTCP=false;
      cnt--; 
      if(!cnt)
       cnt=0xfffe;  //ждем 0  
  }
}


boolean RcvInt(byte r, unsigned int* v )
{
  const byte STR_LEN_INT_PARAM=5;
  static char s[STR_LEN_INT_PARAM];
  static byte ind;

  if(r==',')
  {
    s[ind]=0;
    if(v)
     *v=atoi(s);
    ind=0;
    return true; 
  }
  s[ind]=r;
  ind=min(ind+1,STR_LEN_INT_PARAM);  
  return false;
}

void ProcessAT(byte r, sEventsMap *e);  //wiring - dibiling
/* процесс посимвольного приема данных с модема, 
формирует непустые строки и вызывает разбор команды из sEventsMap *e*/
void ProcessAT(byte r, sEventsMap *e)
  {
    static char* pInpStr=InpStr;
    static char* pNewStr=InpStr;
    
    // отправка сырых данных в различные процессы
    if(RawData)
    {
     RawData(r, NULL);
     return;
    }
    //сохраняем очередной символ в буфере
    *pInpStr=r;
    if(pInpStr<(InpStr+sizeof(InpStr)-1))
      pInpStr++;
   
    //проверяем не начался ли прием данных по TCP и устанавливаем обработчик для них
    if(r==':')
    {
      if(!memcmp("+CIPRCV:",pNewStr,strlen("+CIPRCV:")))
      {
        RawData=ProcessRcvTCP;
        pInpStr=InpStr;
        pNewStr=InpStr;
        return;
      }
     }
    
    if(TestStrEnd(r))
    {
      if((pInpStr-pNewStr)==2) //пропускаем пустые строки
      {
        pInpStr=pNewStr;
        return;
      }
      *pInpStr=0;
      
#ifdef DBG
   DravString(0,7,"<                    ");
   DravString(1,7,pNewStr);
//  delay(3000);
#endif   
      if(ExecAT(InpStr, pNewStr-InpStr, e)) //ищем и исполняем команду, если успешно то удалим из буфера
      {
        pInpStr=InpStr;
      }
      pNewStr=pInpStr;
    }   
  }

void WaitChanal(byte r, void* p)
{
  if(r=='>')
  {
    if(pFnSend)
      SendAT((char*)pFnSend(false));
    RawData=NULL;
  }
}

const char* AT_SEND="AT+CIPSEND=0,";

//инициируем отправку запроса, формируемого в pFnReq и отправляем поток ответа в pFnAnsv
boolean SendTCP(const char* (*pFnReq)(boolean FlBegin), void (*pFnAnsv)(byte r, boolean FlBegin))
{
  if((!pFnReq) || (!pFnAnsv))
    return false;
   
//  char s[strlen(AT_SEND)+6];
  char s[20];
  strcpy(s,AT_SEND);

  itoa(strlen(pFnReq(true)),s+strlen(AT_SEND),10);
  pFnRcv=pFnAnsv;
  pFnSend=pFnReq;
  SendAT(s);
  RawData=WaitChanal;
  FlagsProcessRcvTCP=true;
  return true;
}

//*****************************************************



void loop() {
  int T=millis();

  for(;Serial.available();)  //выгребаем все что принято, парсим и исполняем согласно 
  {
    ProcessAT(Serial.read(), EventsMap);
  }

  
  if(StatModem==UNKNOWN)
  {
    static byte ww;
    
    SendAT(F("AT"));
    digitalWrite(13,ww&1);
    ww++;
    delay(ww%200?24:200);
    
    return;
  }


  if(pFnTimer)
  {
    if(T-PauseTimer>0)
    {
     pFnTimer();
     pFnTimer=NULL;
    }
  }
  

  
  static int TimeCSQ;
  if(ActivAT || (StatModem!=END_INIT))
   TimeCSQ=T;
  else if(T-TimeCSQ>1000)
  {
    static byte AT_state;
   TimeCSQ=T;
    switch(AT_state)
    {
     case 1:     SendAT(F("AT+CIPSTART=\"TCP\",\"78.46.72.113\",80"));break;
     case 2:     SendTCP(http_get, http_recive); break;
    case 11:     SendAT(F("at+csq"));break;
  //    case 12:     SendAT(F("AT+CIPSTART=\"TCP\",\"78.46.72.113\",80"));break;
 // case 13:     SendTCP(http_get, http_recive); break;
 //   case 8:     SendAT(F("AT+CIPCLOSE=0"));break;
 
  //   case 9:     SendAT(F("AT+CIPSHUT=0"));break;
    case 18:     SendAT(F("at+csq"));break;
   
    }
    if(AT_state<18)
     AT_state++;

  }

}

//парсим поток и ищим нужные данные, как найдем - выводим на экран.
void http_recive(byte r, boolean FlBegin)
{
  static byte y;
  static byte state;
  static byte cnt;
  static char buf[21];

  if(FlBegin)
  { 
    DravString(4,3,"R E C I V E");
    state=0;
  }
 
  switch(state)
  {
    case 0:
     FiltrTag_h4.Reset();
     cnt=0;
    case 1:
      if(FiltrTag_h4.Find(r))
      {
         y=0;
        cnt++;
        if(cnt==2)
        {
          state=2;
          ClrScr();
          cnt=0;
        }
      }
      else
        state=1;
      break; 
    case 2:
      if(!(r=ConvUTF8(r)))
        break;
     if(r=='<')
        r=0;  
      buf[cnt]=r;
      cnt++;
       if((cnt==20) || !r) 
      {
        buf[20]=0;
        cnt=0;
        state=3;     
        DravString(0,1,buf);
      }
      
      break;  
    case 3:
      if(FiltrTag_A.Find(r))
        state=4;
      break;
    case 4:
      if(r=='>')
      {
        cnt=0;
        state=5;
      }
      break;
    case 5:
      if(!(r=ConvUTF8(r)))
        break;
       if(r=='<')
        r=0;  
       buf[cnt]=r;
       cnt++;
       if((cnt==20) || !r) 
       {
         buf[20]=0;
         DravString(0,y+3,buf);
   
         y++;
         if(y==5)
         {
           state=1;
           cnt=2;
           y=0;
         }
         else
          state=3;     
      }
      break;  
  }
}


const char* http_get(boolean FlBegin)
{
  if(FlBegin)
  {
    ClrScr();
    DravString(6,3,"S E N D");
  }
  return "GET / HTTP/1.0\r\nHost: arduino.ru\r\n";
}

В принципе делает все тоже, но более правильно и потенциально готов к доработке поддержки нескольких соединений одновременно. Интересно, сколько он может соединений по максимуму держать.

Skarm
Offline
Зарегистрирован: 11.05.2017
Logik
Offline
Зарегистрирован: 05.08.2014

Глянул. С изложеным согласен, но ща это уже не ново. И не полно. Часики стянул себе, сейчас попробую.

Logik
Offline
Зарегистрирован: 05.08.2014

Проверил часики, отрабатывают, но выдают неверное, типа:

"12/06/01,10:01:28+00"

шозанах? Я в америке и в прошлом? секунды и минуты считаются

Skarm
Offline
Зарегистрирован: 11.05.2017

Logik разобрался откуда у длинны данных (1400) ноги растут? Откуда взялось это значение?

А на счёт времени это вопрос к своему опсосу kyivstar наверное по гейропскому времени живут:-) 

Logik
Offline
Зарегистрирован: 05.08.2014

Максимальный размер пакета. Или буфера. Или еще какой хрени. В общем оно не существенно вобще.

Чето я сомниваюсь что в европе сейчас 10 часов. Да и минуты совсем не в тему идут. Но идут!

А воще должно быть UTC, т.е. гринвич, а оте нолики справа - часовой пояс.

По значению судя - это кавказ. Армянское или Кадыровское время.

Skarm
Offline
Зарегистрирован: 11.05.2017

1400 это MTU 

Так часовой пояс +00

Logik
Offline
Зарегистрирован: 05.08.2014

1400 по сути да, MTU, но не из обычных сетей, в езернете оно 1500 или немного меньше. Видать у мобильных его значение нетрадиционное, или просто у А6 так. Но вобщем оно до фени.

Logik
Offline
Зарегистрирован: 05.08.2014

Прошло почти пол суток, а часы попрежнему дают

"12/06/01,10:07:24+00"

))))

Отсчет начинается по включению с 10:00. Надеюсь что это хотяб от оператора зависит, может у других заработает.

Skarm
Offline
Зарегистрирован: 11.05.2017

Установи принудительно время

AT+CCLK="17/05/15,11:33:40+8"

Logik
Offline
Зарегистрирован: 05.08.2014

Чтоб установить время, его надо знать ;)

На кой оно тогда такое сдалось? 

Skarm
Offline
Зарегистрирован: 11.05.2017

Ну на мобиле ты же время устанавливает?? Думаю тут тоже самое.

Logik
Offline
Зарегистрирован: 05.08.2014

Нет конечно, не то же. В мобилке можна выбрать "синхронизировать время по сети". Возьмет его из GSMа.

В принципе через модем время можна получить 

1. С сети GSM - чего тут не получается, вероятно из-за козней оператора, у меня симка специальная для GPRS, на неё (и с неё тоже) нельзя звонить и СМС слать. Про время мы с опсосом не договаривались ;) вот он сука и того... Скорей всего.

2. Из интернета - есть спецпротокол, его ПК использует. Т.к. ТСР работает, то должно срабатывать полюбому.

3. Из инета, залезть на подходящий сайт и с его страницы извлеч время. Точность пострадает но для 99% случаев хватит с головой.

b707
Offline
Зарегистрирован: 26.05.2017

Вот тут https://electronics.stackexchange.com/questions/82090/what-at-command-should-be-sent-to-modem-to-get-time в комментах нашел любопытную последовательность команд для обновления времени:

AT+CLTS=1  // toggle time sync from GSM
AT+COPS=2 // de-register from network
AT+COPS=0 // register

Вроде как после выхода из сети и повторной регистрации время в девайсе должно обновится. Все команды отрабатывают на нашем А6, правда время не меняется :(  Может от сети зависит - я пробовал на Мегафоне.

 

А при наличии интернета - проще всего запросить любую страницу с любого HTTP сервера, в заголовке HTTP-ответа всегда указано время. Для экономии трафика лучше даже запрашивать несуществующую страницу - ответ будет "404 страница не найдена", но время в заголовке будет все равно.

 

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

b707, для экономии трафика следует использовать специальный протокол NTP - в смысле для получения времени. Я пробовал с ethernet shield - работает. Примеры доступны.

b707
Offline
Зарегистрирован: 26.05.2017

toc пишет:
b707, для экономии трафика следует использовать специальный протокол NTP - в смысле для получения времени. Я пробовал с ethernet shield - работает. .

Пожалуйста, вчитайтесь в заголовок темы. Речь идет о TCP на модеме через голый GRPS, что совсем не то же самое, что на готовом Интернет-шильде с кучей библиотек.  C ethernet shield и библиотекой это любой ребенок сможет. А на модеме в виде низкоуровневых UDP -запросов примеры есть? С удовольствием посмотрел бы пример кода.

b707
Offline
Зарегистрирован: 26.05.2017

b707 пишет:

Вот тут https://electronics.stackexchange.com/questions/82090/what-at-command-should-be-sent-to-modem-to-get-time в комментах нашел любопытную последовательность команд для обновления времени:

AT+CLTS=1  // toggle time sync from GSM
AT+COPS=2 // de-register from network
AT+COPS=0 // register

Вроде как после выхода из сети и повторной регистрации время в девайсе должно обновится. Все команды отрабатывают на нашем А6, правда время не меняется :(  Может от сети зависит - я пробовал на Мегафоне.

++++ добавка - попробовал эти же команды с симкой Билайн - работает! Причем время обновилось даже без необходимости выхода и повторной регистрации в сети - сразу же после команды AT+CLTS=1.

Logik
Offline
Зарегистрирован: 05.08.2014

Попробовал AT+CLTS=1. Результата не дало. От сети время зависит, тут без вариантов, сеть не выдала - сушите весла. А вытаскивать время из HTTP GET - таки да! Кудаж ему дется, получится.

b707
Offline
Зарегистрирован: 26.05.2017

Logik пишет:

Попробовал AT+CLTS=1. Результата не дало.

Logik, а выходить из сети и регистрироваться обратно пробовали? Я сейчас несколько раз потестил - одного только AT+CLTS=1  хватает не всегда, но с  AT+COPS=2  результат стабильный.

ЗЫ С интересои изучаю Ваш код - он полезнее, чем даташит на А6 :)

Logik
Offline
Зарегистрирован: 05.08.2014

Поправлю себя, время вытаскивать не из GET конечно, а из ответа на него, из записи типа Last-Modified: Sat, 16 Jan 2010 21:16:42 GMT 

Пробовал

  AT+CLTS=1
  AT+COPS=2
  AT+COPS=0
  AT+CCLK

Причем последнее многократно повторяется каждые 2 секунды. Без результата, время не установилось а идет начиная с 10:00:00 в момент включения. Обещаный "+CTZU:" тоже не пришел ((

Logik
Offline
Зарегистрирован: 05.08.2014

Отрефакторил код функции http_recive из предыущего кода. Она принимает поток TCP (в нашем случае ответ по HTTP) и должна быть и быстрой и гибкой (для разных сайтов разное искать и по разному) и без больших буферов. В общем очень напряжная. Теперь вместо старой http_recive нужно так.

class StreamString
{
	byte L;
	byte* b;
	byte e;
	byte ind;
	
  public:
	void Init(byte* buf, byte LenBuf, byte EndChar=0)
	{
	  ind=0;	
	  b=buf;
      L=LenBuf;
      e=EndChar;	  
	}
	byte Process(byte r)
	{
	    if(r==e)
		{
		  L=ind;
		  *b=0;
		}
		if(ind==L)
		  return ind;
			
	    *b=r;
		b++;
		ind++;
		return 0;
	}
};

StreamString sdp;
byte buf[22];

//Процесс захвата строки из потока учитывая возможность наличия в ней кирилицы
boolean StreamCirStr(byte r)
{
 if(r=ConvUTF8(r))
  if(sdp.Process(r))
  {
    buf[sizeof(buf)-1]=0;
   return true;
  }
 return false;
}

//Инициируем захват строки из потока до указаного символа но не более длины буфера
void InitStreamTegString()
{
  sdp.Init(buf, sizeof(buf), '<');
}

//парсим поток и ищим нужные данные, как найдем - выводим на экран.
void http_recive(byte r, boolean FlBegin)
{
  static byte cnt;
  static byte state;
  
  if(FlBegin)
  { 
    DravString(4,3,"R E C I V E");
    FiltrTag_h4.Reset();
    cnt=0;
    state=0;
  }
  
  switch(state)
  {
    case 0:
      if(FiltrTag_h4.Find(r)) //ждем тега h4
      {
        cnt++;
        if(cnt==2)  //нам нужно второе обявление тега
        {
          ClrScr();
          InitStreamTegString(); //собираем содержимое тега 
          state++;
        }
      }
      break;

     case 1:
       if(StreamCirStr(r))
       {
          DravString(0,1,(char*)buf);
          state++;
       }
       break;
     case 2:
       if(FiltrTag_A.Find(r))  //ждем открытия тега
         state++;
       break;
     case 3:
       if(r=='>')   //ждем конца обявления тега
       {
          InitStreamTegString(); //собираем содержимое тега 
          state++;
       }
       break;
     case 4: 
       if(StreamCirStr(r))
       {
          DravString(0,cnt+1,(char*)buf);
          cnt++;
          if(cnt<7)
            state=2;
          else
            state++;
       }
       break;  
  }
}


А делает все так же, как и было. Зато теперь проще писать  http_recive под каждый конкретный сайт. Класс вынес в либку. 

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

 

ПС. Снова попробовал поднять скорость с 38400 до 57600. Нет. Похоже узкое место - вывод на экран. Похоже за время отрисовки строки приемный буфер Serial переполняется.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logik пишет:

ПС. Снова попробовал поднять скорость с 38400 до 57600. Нет. Похоже узкое место - вывод на экран. Похоже за время отрисовки строки приемный буфер Serial переполняется.

Ну, это легко проверить - Serial.available возвращает как раз заполненную длину буфера.

Если предположение подтвердится, строку можно выводить посимвольно, чередуя с проверкой последовательного порта.

 

PS. По моим прикидкам OLED 0.96 I2C на скорости 400 кГц способен выводить символ примерно за 0.3 мс, что меньше скорости порта на 38400 или 57600. Но стОит проверить библиотеку дисплея - какую скорость обеспечивает она, не исключено, что на порядок меньше максимально возможной.

Skarm
Offline
Зарегистрирован: 11.05.2017

Какой интерфейс у экрана?

 

Logik
Offline
Зарегистрирован: 05.08.2014

andriano пишет:

Logik пишет:

ПС. Снова попробовал поднять скорость с 38400 до 57600. Нет. Похоже узкое место - вывод на экран. Похоже за время отрисовки строки приемный буфер Serial переполняется.

Ну, это легко проверить - Serial.available возвращает как раз заполненную длину буфера.

Если предположение подтвердится, строку можно выводить посимвольно, чередуя с проверкой последовательного порта.

Уже подтвердилось)) Оказалось достаточно закоментировать ClrScr(); и заработало на 57600. Очистка экрана - тяжкое дело. На ней буфер и переполнялся. А дальше чегото конечно терялось, но видать ненужное, и шел процесс выгребания из буфера, но не сильно быстро, на разности скоростей заполнения и извлечения. И к моменту прихода интересной инфы буфер был  (вероятно) достаточно заполнен. Вот оно после нескольких строк и вылетало.

Про вывод посимвольно - как бы да но в реале есть подводный камень. Один символ требует передачи координат и пр. служебной инфы типа адреса на i2c . Для строки оно только раз для первого передается. Можна конечно обойти, но либу работы с экраном прийдется сильно перепахать.

ПС. Прикинул при частоте i2c 800КГц передать 1КБ, для экрана 1306 это весь экран, занимает 13мсек. А за это время на 57600 прийдут 76 байт. Переполнение однозначно. А вот оценить скорость извлечения из буфера - не просто. Тут проще померить фактическую.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну, это хорошо, если адрес передается один раз на строку. Мне попадалась библиотека, в которой адрес передавался с каждым байтом, т.е. по 6 раз на каждый символ.

Logik
Offline
Зарегистрирован: 05.08.2014

Так нет придела кривости софта ))))

b707
Offline
Зарегистрирован: 26.05.2017

Logik, Вы тут больше всех опыта имеете :) Скажите, Вы заморачивались хард-ресетом модуля с ардуины замыканием RST пина на землю? Если делали - то как, напрямую через ардуину или через управляющую цепь, реле или мосфет?

А на тему HTTP вот еще интересную ссылку нашел - может пригодится https://gitlab.me-soldesign.com/Tobias/A6HTTPLibrary/tree/master

 

 

 

Logik
Offline
Зарегистрирован: 05.08.2014

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

Про либу - плохо. Софтсириал и сирмал одновременно не работают. У софтсириал вобще конфликт между передачей и приемом, он сам себя принять не может. Этот конфликт - проблема при приеме асинхронного ответа. Кстати этот ответ приходит с "+CIPRCV" чего вобще в либе не видно. Потому как все принятое просто отправляется в сириал с метками времени. Этот подход просто для попробовать, поигратся годится. Ну а это:

String A6HTTP::A6read() {
  String reply = "";
.....
  return reply;
}

- очень грубая ошибка.

b707
Offline
Зарегистрирован: 26.05.2017

Просто RST на выход ардуины боязно - в даташите ток через RST обозначен до 70ма. Хотя надо будет проверить... может на реальном модеме меньше.

Что касается либы - извиняйте, что так вышло, я в нее подробно не вглядывался. Насколько понял, там автор упор делал на отправку HTTP PUT запроса, а прием ответа он не прорабатывал.  Вообще, мне по инету попадались три-четыре набора библиотек, связанных с нашим A6. Но все либо недоделанные, либо написано очень коряво. Если делать свою библиотеку - проще всего взять за основу либу для SIM800/900 шилда, ибо все равно работа с модемом идет на основе AT-команд, а они практически те же. Я тут начал слегка патчить код SIM900 под наш модем - вполне получается, тестовые примеры отправки, чтения, стирания СМС из библиотеки 900-ого шилда заработали на нашем модеме. А  некоторые процедуры вообще работают без правки.

 

b707
Offline
Зарегистрирован: 26.05.2017

Logik пишет:
Ну а это:

String A6HTTP::A6read() {
  String reply = "";
.....
  return reply;
}

- очень грубая ошибка.

кстати... а почему?

Logik
Offline
Зарегистрирован: 05.08.2014

reply локальная переменная, распологается в стеке и существует только до завершения работы функции, вернув указатель на неё наружу получим возможность обращатся к ней после того как её область памяти освобождена. Но  свободной память будет не долго, туда разместится нечто другое и соответственно при чтении мы получим искаженные данные а при записи испортим то, что туда попало.  Самое печальное что такой код иногда будет работать нормально, но подглючивать, а после некоторых доработок может и стабильно неработать, при попытке локализовать проблему снова заработает или начнут портится данные в других модулях, никак не связаных с этим. И в поведении глюка логику обнаружить невозможно, начинает валится там, где не трогали, а попускает при правке совсем непричастного. В общем страшный сон програмера..

b707
Offline
Зарегистрирован: 26.05.2017
int foo() {
int result=0;
......
return result;
	}

Вроде как возврат значения локальной переменной как результирующее значение процедуры допускается, разве нет?

Logik
Offline
Зарегистрирован: 05.08.2014

возврат значения - да, а указателя на локальную переменную - ошибка. Т.к. этой переменной уже нет. Стоит в вашем примере добавить два символа как он становится ошибкой с описаными выше свойствами.

 
int   *   foo() {
int result=0;
......
return   &result;
	}
А все что сложней скаляра - всегда по указателю идет.