Last active
April 29, 2018 22:39
-
-
Save gabonator/88b80d9c6b16d699d6f4234ebc3a2587 to your computer and use it in GitHub Desktop.
SIM900 nonblocking and reliable gprs http request for arduino and soft serial (at command logger)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GPRS Initialization | |
0: Send AT | |
1: Expect 'OK ' Got '' = no | |
1: Timeout 1000 | |
1: Expect 'OK ' Got '' = no | |
1: Timeout -1 !!!TIMEOUT ERROR!!! | |
2: Send AT | |
3: Expect 'OK ' Got '' = no | |
3: Timeout 1000 | |
3: Expect 'OK ' Got '' = no | |
3: Timeout -1 !!!TIMEOUT ERROR!!! | |
*** ERROR: no AT response buffer: '' | |
3: Goto -1 | |
0: DigitalWrite 4, 0 | |
1: Sleep 1000 | |
2: DigitalWrite 4, 1 | |
4: DigitalWrite 4, 0 | |
6: Return 1 | |
0: Send AT | |
1: Expect 'OK ' Got 'AT ' = no | |
1: Timeout 1000 | |
1: Expect 'OK ' Got 'AT ' = no | |
1: Timeout 998 | |
1: Expect 'OK ' Got 'AT OK ' = yes | |
2: Goto 4 | |
4: Send ATE0 | |
5: Expect 'OK ' Got 'ATE0 ' = no | |
5: Timeout 1000 | |
5: Expect 'OK ' Got 'ATE0 ' = no | |
5: Timeout 998 | |
5: Expect 'OK ' Got 'ATE0 OK ' = yes | |
6: Send ACMEE=1 | |
7: Expect 'OK ' Got 'ATE0 OK ' = yes | |
8: Send AT+CFUN=1 | |
9: Expect 'OK ' Got ' ' = no | |
9: Timeout 1000 | |
9: Expect 'OK ' Got ' OK ' = yes | |
10: Send AT+CPIN? | |
11: Expect '+CPIN: READY ' Got ' ' = no | |
11: Timeout 1000 | |
11: Expect '+CPIN: READY ' Got ' +CPIN: READY ' = yes | |
12: Expect 'OK ' Got ' ' = no | |
12: Timeout 1000 | |
12: Expect 'OK ' Got ' OK ' = yes | |
13: Return 1 | |
Starting upload | |
0: Send AT+CSTT="o2internet","","" | |
1: Expect 'OK ' Got ' ' = no | |
1: Expect '+CME ERROR' Got ' ' = no | |
1: Timeout 20000 | |
1: Expect 'OK ' Got ' OK ' = yes | |
2: Expect '+CME ERROR' Got ' OK ' = no | |
2: Send AT+CIICR | |
3: Expect 'OK ' Got ' ' = no | |
3: Timeout 1000 | |
3: Expect 'OK ' Got ' OK ' = yes | |
4: Send AT+CIFSR | |
5: Timeout 1000 | |
Valid IP in buffer: 100.126.133.135 | |
6: Send AT+CIPSTART="TCP","api.*****.***",80 | |
7: Expect 'CONNECT OK ' Got ' ' = no | |
7: Expect 'ALREADY CONNECTED' Got ' ' = no | |
7: Timeout 10000 | |
7: Expect 'CONNECT OK ' Got ' OK ' = no | |
7: Expect 'ALREADY CONNECTED' Got ' OK ' = no | |
7: Timeout 9997 | |
7: Expect 'CONNECT OK ' Got ' OK ' = no | |
7: Expect 'ALREADY CONNECTED' Got ' OK ' = no | |
7: Timeout 9151 | |
7: Expect 'CONNECT OK ' Got ' OK ' = no | |
7: Expect 'ALREADY CONNECTED' Got ' OK ' = no | |
7: Timeout 8980 | |
7: Expect 'CONNECT OK ' Got ' OK CONNECT OK ' = yes | |
8: Expect 'ALREADY CONNECTED' Got ' OK CONNECT OK ' = no | |
8: Send AT+CIPSEND=95 | |
9: Expect '>' Got ' ' = no | |
9: Timeout 5000 | |
9: Expect '>' Got ' > ' = yes | |
10: Send GET /*****/?n=123 HTTP/1.0 Host: api.*****.*** User-Agent: sim900 on esp8266 by valky.eu | |
11: Expect 'SEND OK ' Got ' ' = no | |
11: Timeout 5000 | |
11: Expect 'SEND OK ' Got ' SEND OK ' = yes | |
12: Expect 'CLOSED ' Got ' SEND OK ' = no | |
12: Timeout 20000 | |
12: Expect 'CLOSED ' Got 'HTTP/1.1 200 OK ' = no | |
12: Timeout 19918 | |
12: Expect 'CLOSED ' Got 'Server: openresty ' = no | |
12: Timeout 19898 | |
12: Expect 'CLOSED ' Got 'Date: Sun, 29 Apr 2018 17:24:40 GMT ' = no | |
12: Timeout 19860 | |
12: Expect 'CLOSED ' Got 'Content-Type: text/html ' = no | |
12: Timeout 19834 | |
12: Expect 'CLOSED ' Got 'Connection: close ' = no | |
12: Timeout 19814 | |
12: Expect 'CLOSED ' Got 'Access-Control-Allow-Origin: * ' = no | |
12: Timeout 19781 | |
12: Expect 'CLOSED ' Got 'Access-Control-Allow-Methods: POST, GET, OPTIONS ' = no | |
12: Timeout 19727 | |
12: Expect 'CLOSED ' Got 'Access-Control-Allow-Headers: X-Requested-With, content-type ' = no | |
12: Timeout 19663 | |
12: Expect 'CLOSED ' Got 'Vary: Accept-Encoding,User-Agent ' = no | |
12: Timeout 19628 | |
12: Expect 'CLOSED ' Got 'Vary: Accept-Encoding,User-Agent ' = no | |
12: Timeout 19625 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 19617 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 18999 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 17998 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 16997 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 15996 | |
12: Expect 'CLOSED ' Got 'Moze byt ' = no | |
12: Timeout 15466 | |
12: Expect 'CLOSED ' Got 'CLOSED ' = yes | |
13: Send AT+CIPSTATUS | |
14: Expect 'CONNECTED ' Got ' ' = no | |
14: Expect 'STATE: TCP CLOSED ' Got ' ' = no | |
14: Timeout 5000 | |
14: Expect 'CONNECTED ' Got ' OK ' = no | |
14: Expect 'STATE: TCP CLOSED ' Got ' OK ' = no | |
14: Timeout 4995 | |
14: Expect 'CONNECTED ' Got ' OK ' = no | |
14: Expect 'STATE: TCP CLOSED ' Got ' OK ' = no | |
14: Timeout 4985 | |
14: Expect 'CONNECTED ' Got ' OK STATE: TCP CLOSED ' = no | |
14: Expect 'STATE: TCP CLOSED ' Got ' OK STATE: TCP CLOSED ' = yes | |
15: Goto 17 | |
17: Send AT+CIPSHUT | |
18: Expect 'SHUT OK ' Got ' ' = no | |
18: Timeout 5000 | |
18: Expect 'SHUT OK ' Got ' SHUT OK ' = yes | |
19: Return 1 | |
Upload ok |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Efficient and reliable nonblocking SIM900 driver for arduino | |
// Analyze and debug AT commands & responses easily | |
// Suitable for cooperative multitasking applications | |
// tinysine SIM900 Quad-band GSM/GPRS Shield for Arduino UNO/MEGA/Leonardo | |
#include "SoftwareSerial.h" | |
#define pinTX D3 // TX: pin D3 of ESP8266 connected to pin D2 of SIM900 | |
#define pinRX D2 // RX: pin D2 of ESP8266 connected to pin D3 of SIM900 | |
#define pinPower D4 // PWRKEY: pin D4 of ESP8266 connected to pin D8 of SIM900 | |
const char* _urlHost = "api.*****.***"; | |
const char* _urlPath = "/vendea/?n=%d&time=%lu"; | |
void buildUrl(char* url) | |
{ | |
static int counter = 0; | |
sprintf(url, _urlPath, counter++, millis()); | |
} | |
SoftwareSerial soft(pinTX, pinRX); | |
//#define QUIET | |
#ifdef QUIET | |
#define Error_print Serial.print | |
#define Debug_print(x) void() | |
#define Debug_printnocrlf(x) void() | |
#else | |
#define Debug_print Serial.print | |
#define Error_print Serial.print | |
#define Debug_printnocrlf Error_printnocrlf | |
#endif | |
void Error_printnocrlf(const char* msg) { Error_printnocrlf((char*)msg); } | |
void Error_printnocrlf(char* msg) | |
{ | |
while (*msg) | |
{ | |
char c = *msg++; | |
if ( c!=0x0d && c!=0x0a) | |
Debug_print(c); | |
else | |
Debug_print(' '); | |
} | |
} | |
class CProgram | |
{ | |
int mPc{0}; | |
CProgram* mChild{nullptr}; | |
bool mHasReturnCode{false}; | |
bool mReturnCode{false}; | |
long mFrozenTill{0}; | |
int mLastTimeoutLabel{-1}; | |
long mLastTimeout{0}; | |
bool bThisFrameJumped{false}; | |
int mBufferPos{0}; | |
bool mPipe{false}; | |
protected: | |
char mBuffer[128]; | |
public: | |
void Reset() | |
{ | |
mHasReturnCode = false; | |
mPc = 0; | |
mLastTimeoutLabel = 0; | |
} | |
void Pipe(bool b) | |
{ | |
mPipe = b; | |
} | |
void operator << (char c) | |
{ | |
if (mPipe) | |
{ | |
static bool flushOnChar = false; | |
if (c == 0x0d || c == 0x0a) | |
{ | |
flushOnChar = true; | |
//mBufferPos = 0; | |
} else | |
{ | |
if (flushOnChar) | |
{ | |
mBufferPos = 0; | |
flushOnChar = false; | |
} | |
} | |
if (mBufferPos >= sizeof(mBuffer)-1) | |
return; | |
} | |
if (mBufferPos >= sizeof(mBuffer)-1) | |
{ | |
Serial.print("Buffer overflow\n"); | |
return; | |
} | |
mBuffer[mBufferPos++] = c; | |
mBuffer[mBufferPos] = 0; | |
} | |
int Pc() | |
{ | |
return mPc; | |
} | |
virtual void Program() | |
{ | |
Serial.print("No program defined\n"); | |
} | |
void Sleep(int x) | |
{ | |
mFrozenTill = millis() + x; | |
Debug_print(Pc()); | |
Debug_print(": Sleep "); | |
Debug_print(x); | |
Debug_print("\n"); | |
} | |
void DigitalWrite(int p, int v) | |
{ | |
Debug_print(Pc()); | |
Debug_print(": DigitalWrite "); | |
Debug_print(p); | |
Debug_print(", "); | |
Debug_print(v); | |
Debug_print("\n"); | |
digitalWrite(p, v); | |
Next(); | |
} | |
void Send(const char* p) | |
{ | |
mBufferPos = 0; | |
mBuffer[0] = 0; | |
Debug_print(Pc()); | |
Debug_print(": Send "); | |
Debug_printnocrlf(p); | |
Debug_print("\n"); | |
soft.print(p); | |
Next(); | |
} | |
int BufferLength() | |
{ | |
return (int)mBufferPos; | |
} | |
char BufferAt(int i) | |
{ | |
//assert(i>=0 && i<mBufferPos); | |
return mBuffer[i]; | |
} | |
bool Expect(const char* p) | |
{ | |
bool found = (strstr(mBuffer, p) != nullptr); | |
Debug_print(Pc()); | |
Debug_print(": Expect '"); | |
Debug_printnocrlf(p); | |
Debug_print("' Got '"); | |
Debug_printnocrlf(mBuffer); | |
Debug_print("' = "); | |
Debug_print(found ? "yes" : "no"); | |
Debug_print("\n"); | |
if (found) | |
{ | |
mBufferPos = 0; | |
Next(); | |
} | |
return found; | |
} | |
bool Timeout(int n) | |
{ | |
if (bThisFrameJumped) | |
return false; | |
bool passed = false; | |
long now = millis(); | |
if (mLastTimeoutLabel != Pc()) | |
{ | |
mLastTimeoutLabel = Pc(); | |
mLastTimeout = now; | |
} else | |
{ | |
if (now > mLastTimeout + n) | |
passed = true; | |
} | |
if (passed) | |
{ | |
Error_print("*** GPRS "); | |
Error_print(Pc()); | |
Error_print(" TIMEOUT: "); | |
Error_print(n); | |
Error_print(" buffer: '"); | |
Error_printnocrlf(mBuffer); | |
Error_print("'\n"); | |
} else | |
{ | |
Debug_print(Pc()); | |
Debug_print(": Timeout "); | |
Debug_print(mLastTimeout + n - now); | |
if (passed) | |
Debug_print(" !!!TIMEOUT ERROR!!!"); | |
Debug_print("\n"); | |
} | |
return passed; | |
} | |
void Error(const char* message) | |
{ | |
Error_print("GPRS *** "); | |
Error_print(Pc()); | |
Error_print(" ERROR: "); | |
Error_print(message); | |
Error_print(" buffer: '"); | |
Error_printnocrlf(mBuffer); | |
Error_print("'\n"); | |
} | |
void Call(CProgram& program) | |
{ | |
mChild = &program; | |
mChild->Reset(); | |
} | |
void Return(bool b) | |
{ | |
Debug_print(Pc()); | |
Debug_print(": Return "); | |
Debug_print(b); | |
Debug_print("\n"); | |
mHasReturnCode = true; | |
mReturnCode = b; | |
} | |
bool HasReturnCode(bool& ret) | |
{ | |
ret = mReturnCode; | |
return mHasReturnCode; | |
} | |
void Goto(int label) | |
{ | |
bThisFrameJumped = true; | |
Debug_print(Pc()); | |
Debug_print(": Goto "); | |
Debug_print(label); | |
Debug_print("\n"); | |
mPc = label; | |
} | |
void Next() | |
{ | |
bThisFrameJumped = true; | |
mPc++; | |
} | |
void operator()() | |
{ | |
if (mHasReturnCode) | |
return; | |
if (mFrozenTill != 0) | |
{ | |
if (millis() < mFrozenTill) | |
return; | |
mFrozenTill = 0; | |
Next(); | |
return; | |
} | |
if (mChild) | |
{ | |
(*mChild)(); | |
bool returnCode; | |
if (mChild->HasReturnCode(returnCode)) | |
{ | |
Next(); | |
mChild = nullptr; | |
} | |
return; | |
} | |
bThisFrameJumped = false; | |
Program(); | |
} | |
}; | |
class CProgToggleSwitch : public CProgram | |
{ | |
public: | |
virtual void Program() | |
{ | |
switch (Pc()) | |
{ | |
case 0: DigitalWrite(pinPower, LOW); break; | |
case 1: Sleep(1000); break; | |
case 2: DigitalWrite(pinPower, HIGH); break; | |
case 3: Sleep(2000); break; | |
case 4: DigitalWrite(pinPower, LOW); break; | |
case 5: Sleep(3000); break; | |
default: Return(true); | |
} | |
} | |
} progToggleSwitch; | |
class CProgInit : public CProgram | |
{ | |
public: | |
virtual void Program() | |
{ | |
switch (Pc()) | |
{ | |
case -2: Call(progToggleSwitch); break; | |
case -1: Call(progToggleSwitch); break; | |
// at response | |
case 0: Send("AT\r\n"); break; | |
case 1: if (Expect("OK\r\n")) Goto(4); | |
if (Expect("NORMAL POWER DOWN")) Goto(-1); | |
if (Timeout(1000)) Next(); | |
break; | |
// at response - second try | |
case 2: Send("AT\r\n"); break; | |
case 3: if (Expect("OK\r\n")) Goto(4); | |
if (Expect("NORMAL POWER DOWN")) Goto(-1); | |
if (Timeout(1000)) { Error("no AT response"); Goto(-1); } break; | |
// disable echo | |
case 4: Send("ATE0\r\n"); break; | |
case 5: Expect("OK\r\n"); if (Timeout(1000)) Goto(0); break; | |
case 6: Send("AT+CMEE=1\r\n"); break; | |
case 7: Expect("OK\r\n"); if (Timeout(1000)) Goto(0); break; | |
case 8: Send("AT+CFUN=1\r\n"); break; | |
case 9: Expect("OK\r\n"); if (Timeout(1000)) Goto(0); break; | |
case 10: Send("AT+CPIN?\r\n"); break; | |
case 11: Expect("+CPIN: READY\r\n"); if (Timeout(2000)) { Error("wrong PIN response"); Goto(0); } break; | |
case 12: Expect("OK\r\n"); if (Timeout(2000)) Next(); break; | |
case 13: Sleep(3000); break; // needs some time before attaching to apn | |
default: Return(true); break; | |
} | |
} | |
} progInit; | |
class CProgTransfer : public CProgram | |
{ | |
char mRequest[256]; | |
bool mRetry{false}; | |
public: | |
bool ReadIpAddress() | |
{ | |
bool begin = true; | |
uint8_t addressBytes = 0; | |
for (int i=0; i<BufferLength(); i++) | |
{ | |
char c = BufferAt(i); | |
if (begin) | |
{ | |
if (c == 0x0d || c == 0x0a) | |
continue; | |
begin = false; | |
} | |
if (c == 0x0d || c == 0x0a) | |
{ | |
Debug_print("Assigned IP address: "); | |
Debug_printnocrlf(mBuffer); | |
Debug_print("\n"); | |
return (addressBytes > 5); | |
} | |
if (c == '.' || (c >= '0' && c <= '9')) | |
{ | |
addressBytes++; | |
continue; | |
} | |
break; | |
} | |
return false; | |
} | |
void Init(const char* host, const char *url) | |
{ | |
mRetry = false; | |
sprintf(mRequest, | |
"GET %s HTTP/1.0\r\n" | |
"Host: %s\r\n" | |
"User-Agent: sim900 on esp8266 by valky.eu\r\n" | |
"\r\n", url, host); | |
} | |
void Retry(int label) | |
{ | |
if (mRetry) | |
Return(false); | |
else | |
{ | |
Debug_print("Retrying...\n"); | |
mRetry = true; | |
Goto(label); | |
} | |
} | |
virtual void Program() | |
{ | |
char buffer[128]; | |
switch (Pc()) | |
{ | |
case 0: Send("AT+CSTT=\"o2internet\",\"\",\"\"\r\n"); break; | |
case 1: Expect("OK\r\n"); | |
if (Expect("+CME ERROR")) { Error("CME Error when attaching apn"); Return(false);} | |
if (Timeout(20000)) Return(false); | |
break; | |
case 2: Send("AT+CIICR\r\n"); break; | |
case 3: Expect("OK\r\n"); if (Timeout(1000)) Next(); break; | |
case 4: Send("AT+CIFSR\r\n"); break; | |
case 5: if (ReadIpAddress()) Next(); if (Timeout(5000)) Return(false); break; | |
case 6: Send("AT+CIPSTART=\"TCP\",\"api.gabo.guru\",80\r\n"); break; | |
case 7: Expect("CONNECT OK\r\n"); | |
Expect("ALREADY CONNECTED"); | |
if (Expect("CONNECT FAIL")) Retry(6); | |
if (Timeout(10000)) Return(false); break; | |
case 8: sprintf(buffer, "AT+CIPSEND=%d\r\n", (int)strlen(mRequest)); Send(buffer); break; | |
case 9: Expect(">"); | |
if (Expect("ERROR")) Retry(8); | |
if (Timeout(5000)) Return(false); break; | |
case 10: Send(mRequest); break; | |
case 11: if (Expect("SEND OK\r\n")) Pipe(true); | |
if (Expect("CLOSED\r\n")) Retry(10); | |
if (Timeout(15000)) Return(false); break; | |
case 12: if (Expect("CLOSED\r\n")) Pipe(false); | |
if (Timeout(20000)) { Pipe(false); Return(false);} break; | |
case 13: Send("AT+CIPSTATUS\r\n"); break; | |
case 14: if (Expect("CONNECTED\r\n")) Goto(15); | |
if (Expect("STATE: TCP CLOSED\r\n")) Goto(17); | |
if (Timeout(5000)) Return(false); break; | |
case 15: Send("AT+CIPCLOSE\r\n"); break; | |
case 16: Expect("CLOSE OK\r\n"); if (Timeout(5000)) Return(false); break; | |
case 17: Send("AT+CIPSHUT\r\n"); break; | |
case 18: Expect("SHUT OK\r\n"); if (Timeout(5000)) Return(false); break; | |
case 19: Sleep(1000); break; // relax for a while | |
default: Return(true); break; | |
} | |
} | |
} progTransfer; | |
class CUploader | |
{ | |
bool uploadProgress{false}; | |
bool lastUploadOk{false}; | |
public: | |
bool isInitOk() | |
{ | |
bool bOk; | |
if (progInit.HasReturnCode(bOk)) | |
return bOk; | |
return false; | |
} | |
bool isTransferOk() | |
{ | |
bool bOk; | |
if (progTransfer.HasReturnCode(bOk)) | |
return bOk; | |
return false; | |
} | |
bool isTransferError() | |
{ | |
bool bOk; | |
if (progTransfer.HasReturnCode(bOk)) | |
return !bOk; | |
return false; | |
} | |
bool isReady() | |
{ | |
return isInitOk() && !uploadProgress; | |
} | |
void request(char* urlHost, char* urlPath) | |
{ | |
if (lastUploadOk) | |
{ | |
Serial.print("Resuming upload\n"); | |
progTransfer.Reset(); | |
progTransfer.Goto(6); | |
} else | |
{ | |
Serial.print("Starting upload\n"); | |
progTransfer.Reset(); | |
} | |
progTransfer.Init(urlHost, urlPath); | |
uploadProgress = true; | |
} | |
void operator()() | |
{ | |
if (!isInitOk()) | |
{ | |
progInit(); | |
return; | |
} | |
if (uploadProgress) | |
{ | |
progTransfer(); | |
if (isTransferOk()) | |
{ | |
Serial.print("Upload ok\n"); | |
uploadProgress = false; | |
lastUploadOk = true; | |
} | |
if (isTransferError()) | |
{ | |
Serial.print("Upload failed, resetting\n"); | |
progInit.Reset(); | |
progInit.Goto(-2); | |
uploadProgress = false; | |
lastUploadOk = false; | |
} | |
} | |
} | |
void operator <<(char c) | |
{ | |
if (!isInitOk()) | |
progInit << c; | |
else | |
{ | |
if (uploadProgress) | |
progTransfer << c; | |
else | |
{ | |
Serial.print("finished, char:"); | |
Serial.println(c); | |
} | |
} | |
} | |
} uploader; | |
void setup() | |
{ | |
soft.begin(9600); | |
pinMode(pinPower, OUTPUT); | |
Serial.begin(115200); | |
Serial.print("\nGPRS Initialization\n"); | |
} | |
void loop() | |
{ | |
static long last = 0; | |
bool shouldProcess = false; | |
yield(); | |
while (soft.available()) | |
{ | |
yield(); | |
char c = soft.read(); | |
uploader << c; | |
if (c == '\n') | |
shouldProcess = true; | |
} | |
long now = millis(); | |
if (shouldProcess || now > last + 1000) | |
{ | |
last = now; | |
uploader(); | |
if (uploader.isReady()) | |
{ | |
char urlPath[128]; | |
buildUrl(urlPath); | |
uploader.request((char*)_urlHost, urlPath); | |
Serial.print("New request "); | |
Serial.print(urlPath); | |
Serial.print("\n"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment