//-------------------------------------------------------------------------- // Copyright 2016, RIoT International Pty Ltd // // @author Aaron Ardiri //-------------------------------------------------------------------------- #include #include // must remember to fix the bug (if an issue) // https://github.com/arduino-libraries/WiFi/issues/5 //-------------------------------------------------------------------------- // configuration //-------------------------------------------------------------------------- #define ENABLE_DEBUG //#define ENABLE_DEBUG_RAM #define ENABLE_PROGMEM //-------------------------------------------------------------------------- // PROGMEM: compatibity scripts; for non PROGMEM devices //-------------------------------------------------------------------------- #ifdef ENABLE_PROGMEM #include #else #undef PROGMEM #undef F #undef pgm_read_byte #define PROGMEM #define F(x) (x) #define memcpy_P(x,y,z) memcpy((x),(y),(z)) #define strcpy_P(x,y) strcpy((x),(y)) #define strlen_P(x) strlen((x)) #define pgm_read_byte(x) *((byte *)(x)) #endif //-------------------------------------------------------------------------- // WiFi credentials //-------------------------------------------------------------------------- // WiFi library fix on Arduino, failed connect messes with sockets // https://github.com/arduino-libraries/WiFi/issues/5 #define MAX_SOCK_NUM 4 const char WiFi_SSID[] PROGMEM = "xxxx"; const char WiFi_PASS[] PROGMEM = "xxxx"; const byte SERVER[] PROGMEM = { xxx, xxx, xxx, xxx }; #define SERVER_NAME "xxx.xxxxxxx.xxx" #define PING_INTERVAL 300 // 5 minutes // this is our global unique identifier; should be unique for each client #define GUID "ffa90e64-f3e2-4697-b98c-e3c13d1b2362" #define GUID_LEN 36 // should match the length of GUID //-------------------------------------------------------------------------- // helper function: RAM is critical; should watch it always //-------------------------------------------------------------------------- #ifdef ENABLE_DEBUG #ifdef ENABLE_DEBUG_RAM int sys_freeRAM() { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); } #endif #endif //-------------------------------------------------------------------------- // session interface //-------------------------------------------------------------------------- void session_create(); void session_ping(); //-------------------------------------------------------------------------- // entry point //-------------------------------------------------------------------------- #define STATE_IDLE 0 #define STATE_SESSION 1 #define STATE_PING 2 WiFiClient client; int app_state; uint32_t app_ts; uint32_t app_ts_ping; uint32_t app_ts_local; char app_session[64]; void network() { int status = WL_IDLE_STATUS; char ssid[32], pass[32]; // copy the credentials from PROGMEM strcpy_P(ssid, WiFi_SSID); strcpy_P(pass, WiFi_PASS); // attempt to connect to the WiFi network do { #ifdef ENABLE_DEBUG Serial.println(F(":: attempting to connect to WiFi network ")); #ifdef ENABLE_DEBUG_RAM Serial.print(F("available RAM: ")); Serial.println(sys_freeRAM()); Serial.println(); #endif #endif status = WiFi.begin(ssid, pass); } while (status != WL_CONNECTED); #ifdef ENABLE_DEBUG Serial.println(F("** connected to WiFi")); Serial.print(F(" SSID: ")); Serial.println(WiFi.SSID()); Serial.print(F(" IP Address: ")); Serial.println(WiFi.localIP()); Serial.print(F(" NetMask: ")); Serial.println(WiFi.subnetMask()); Serial.print(F(" Gateway: ")); Serial.println(WiFi.gatewayIP()); Serial.println(); #endif } void setup() { #ifdef ENABLE_DEBUG Serial.begin(9600); while (!Serial); // wait a while so we can get the serial console running delay(2500); Serial.println(F(":: microTLS (none/none)")); Serial.println(F("=======================")); Serial.println(F("(c) RIoT International Pty Ltd")); Serial.println(F("build: " __DATE__)); Serial.println(); #ifdef ENABLE_DEBUG_RAM Serial.print(F("available RAM: ")); Serial.println(sys_freeRAM()); Serial.println(); #endif #endif // initialize our network network(); // wait a second for the network to settle delay(1000); // initialize our app_ts_local app_ts = 0; app_ts_ping = 0; // we do not know these values at this point app_ts_local = millis(); // first task is to initialize a session with server app_state = STATE_SESSION; } void loop() { uint32_t ts; // update the app_ts, based on CPU milliseconds ts = millis(); app_ts += (ts - app_ts_local) / 1000; // whole seconds app_ts_local = ts - ((ts - app_ts_local) % 1000); // don't forget the modulus #ifdef ENABLE_DEBUG Serial.print(F("app state: ")); Serial.println(app_state); Serial.print(F("current time: ")); Serial.println(app_ts); Serial.print(F("session_ping: ")); Serial.println(app_ts_ping); #ifdef ENABLE_DEBUG_RAM Serial.print(F("available RAM: ")); Serial.println(sys_freeRAM()); Serial.println(); #endif #endif switch (app_state) { case STATE_IDLE: // is it time to ping the server again? if (app_ts >= app_ts_ping) app_state = STATE_PING; else delay(500); // just kill some time while we wait break; case STATE_SESSION: session_create(); break; case STATE_PING: session_ping(); break; } } //-------------------------------------------------------------------------- // base64:: encode and decode //-------------------------------------------------------------------------- const char b64_alphabet[] PROGMEM = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz" \ "0123456789+/"; byte b64_lookup(char c) { if ((c >= 'A') && (c <= 'Z')) return (c - 'A'); if ((c >= 'a') && (c <= 'z')) return (c - 'a') + 26; if ((c >= '0') && (c <= '9')) return (c - '0') + 52; if (c == '+') return 62; if (c == '/') return 63; return -1; } void b64_encode_chunk(byte *src, byte cnt, char *dst) { int x; byte i[3], o[4]; // make sure cnt is between 0..3 cnt = cnt & 0x03; // what are our three input values? for (x=0; x> 2; o[1] = ((i[0] & 0x03) << 4) + ((i[1] & 0xf0) >> 4); o[2] = ((i[1] & 0x0f) << 2) + ((i[2] & 0xc0) >> 6); o[3] = (i[2] & 0x3f); // write the information to the destination for (x=0; x4 byte chunks while (cnt > 0) { len = (cnt > 2) ? 3 : cnt; b64_encode_chunk(p, len, _p); p += len; _p += 4; cnt -= len; } } void b64_encode_stream(Stream *s, byte *src, int cnt) { byte *p = src; char dst[5] = { 0 }; // allow for end of string char int len; // we process max 3 bytes at a time; 3->4 byte chunks while (cnt > 0) { len = (cnt > 2) ? 3 : cnt; b64_encode_chunk(p, len, dst); s->print(dst); p += len; cnt -= len; } } int b64_encode_len(int len) { int n = len; return (n + 2 - ((n + 2) % 3)) / 3 * 4; } void b64_decode_chunk(char *src, byte cnt, byte *dst) { int x; byte i[4], o[3]; // cnt should always be 4 cnt = 4; while (src[cnt-1] == '=') cnt--; if (cnt < 2) { dst[0] = 0; return; } // require min two bytes // what are our three input values? for (x=0; x> 4); o[1] = ((i[1] & 0xf) << 4) + ((i[2] & 0x3c) >> 2); o[2] = ((i[2] & 0x3) << 6) + i[3]; // write the information to the destination for (x=0; x3 byte chunks while (cnt > 0) { len = (cnt > 3) ? 4 : cnt; b64_decode_chunk(p, len, _p); p += len; _p += 3; cnt -= len; } } void b64_decode_stream(Stream *s, char *src, int cnt) { char *p = src; byte dst[4] = { 0 }; // allow for end of string char int len; // we process max 4 bytes at a time; 4->3 byte chunks while (cnt > 0) { len = (cnt > 3) ? 4 : cnt; b64_decode_chunk(p, len, dst); s->print((char *)dst); p += len; cnt -= len; } } void b64_decode_stream_hex(Stream *s, char *src, int cnt) { char *p = src; byte dst[4] = { 0 }; // allow for end of string char int i, len; // we process max 4 bytes at a time; 4->3 byte chunks while (cnt > 0) { len = (cnt > 3) ? 4 : cnt; b64_decode_chunk(p, len, dst); for (i=0;iprint(F("0")); s->print(dst[i], HEX); s->print(F(" ")); } p += len; cnt -= len; } } int b64_decode_len(char *src, int len) { int i, eq = 0; for (i=len-1; (src[i] == '='); i--) eq++; return ((6 * len) / 8) - eq; } //-------------------------------------------------------------------------- // session implementation //-------------------------------------------------------------------------- const char HTTP_OK[] PROGMEM = "HTTP/1.1 200 OK"; #define HTTP_TIMEOUT 5000 // 5 second timeout for HTTP request #define HTTP_MIN_WAIT 500 #define HTTP_HEADER 0 #define HTTP_DATA 1 const char KEY_SESSION[] PROGMEM = "\"session\":"; const char KEY_TS[] PROGMEM = "\"ts\":"; const char KEY_PARAMS[] PROGMEM = "\"params\":"; const char KEY_BUFFER[] PROGMEM = "\"buffer\":"; //-------------------------------------------------------------------------- // our POST request has 3 portions: POST_0 + GUID + POST_1 const char POST_DATA_0[] PROGMEM = "{" \ "\"guid\":\""; const char POST_DATA_1[] PROGMEM = "\"," \ "\"security\":" \ "{" \ "\"protocol\":\"none\"," \ "\"response\":\"none\"," \ "\"protocol_sub\":\"none\"" \ "}" \ "}"; #define MAX_BUFLEN 80 // this should be plenty for our session_create() void session_create() { byte server[4]; int connectLoop; int len0, len1; int i, len, cnt, stat; char c, buf[MAX_BUFLEN]; char *p, needle[16]; // needle must fit KEY_SESSION and KEY_TS byte *b; int b_len; String val; #ifdef ENABLE_DEBUG Serial.println(F(":: attempting connection to server")); #ifdef ENABLE_DEBUG_RAM Serial.print(F("available RAM: ")); Serial.println(sys_freeRAM()); Serial.println(); #endif #endif // we need to copy the server address from PROGMEM memcpy_P(server, SERVER, 4); if (client.connect(server, 80)) { len0 = strlen_P(POST_DATA_0); len1 = strlen_P(POST_DATA_1); // total length is all components combined len = len0 + GUID_LEN + len1; #ifdef ENABLE_DEBUG Serial.println(F("** connected")); // make the HTTP POST request Serial.println(F("-- request:")); Serial.println(F("POST /xxx/index.php HTTP/1.1")); Serial.print(F("Host: ")); Serial.println(F(SERVER_NAME));; Serial.println(F("User-Agent: Arduino/1.0")); Serial.println(F("Connection: close")); Serial.println(F("Content-type: application/x-www-form-urlencoded")); Serial.print(F("Content-Length: ")); Serial.println(len); Serial.println(); for (i=0; i HTTP_TIMEOUT) || ((connectLoop > HTTP_MIN_WAIT) && (!client.connected()))) { #ifdef ENABLE_DEBUG Serial.println(); Serial.println(F(":: done")); #endif // ensure the client is clean while (client.connected()) { while (client.available()) client.flush(); } client.stop(); // did we receive nothing - happens sometimes if (cnt == 0) goto session_create_fail; goto session_create_done; } } session_create_done: ; // since we now have a session; define when to ping the server app_ts_ping = app_ts; // do it as soon as possible // we are done; let the state engine figure out what to do next app_state = STATE_IDLE; session_create_fail: ; } else { #ifdef ENABLE_DEBUG Serial.println(F("** connection failed")); #endif // small bug fix, make sure socket is available in WiFi // https://github.com/arduino-libraries/WiFi/issues/5 for (i=0;i HTTP_TIMEOUT) || ((connectLoop > HTTP_MIN_WAIT) && (!client.connected()))) { #ifdef ENABLE_DEBUG Serial.println(); Serial.println(F(":: done")); #endif // ensure the client is clean while (client.connected()) { while (client.available()) client.flush(); } client.stop(); // did we receive nothing - happens sometimes if (cnt == 0) goto session_ping_fail; goto session_ping_done; } } session_ping_done: ; // since we now have a session; define when to ping the server app_ts_ping = app_ts + PING_INTERVAL; // we are done; let the state engine figure out what to do next app_state = STATE_IDLE; session_ping_fail: ; } else { #ifdef ENABLE_DEBUG Serial.println(F("** connection failed")); #endif // small bug fix, make sure socket is available in WiFi // https://github.com/arduino-libraries/WiFi/issues/5 for (i=0;i