Blynk library for embedded hardware. Works with Arduino, ESP8266, Raspberry Pi, Intel Edison/Galileo, LinkIt ONE, Particle Core/Photon, Energia, ARM mbed, etc. http://www.blynk.cc/

Dependents:   Blynk_RBL_BLE_Nano Blynk_MicroBit Blynk_Serial Blynk_RBL_BLE_Nano

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers BlynkProtocol.h Source File

BlynkProtocol.h

Go to the documentation of this file.
00001 /**
00002  * @file       BlynkProtocol.h
00003  * @author     Volodymyr Shymanskyy
00004  * @license    This project is released under the MIT License (MIT)
00005  * @copyright  Copyright (c) 2015 Volodymyr Shymanskyy
00006  * @date       Jan 2015
00007  * @brief      Blynk protocol implementation
00008  *
00009  */
00010 
00011 #ifndef BlynkProtocol_h
00012 #define BlynkProtocol_h
00013 
00014 #include <string.h>
00015 #include <stdlib.h>
00016 #include <Blynk/BlynkDebug.h>
00017 #include <Blynk/BlynkProtocolDefs.h>
00018 #include <Blynk/BlynkApi.h>
00019 #include <utility/BlynkUtility.h>
00020 
00021 template <class Transp>
00022 class BlynkProtocol
00023     : public BlynkApi< BlynkProtocol<Transp> >
00024 {
00025     friend class BlynkApi< BlynkProtocol<Transp> >;
00026 public:
00027     enum BlynkState {
00028         CONNECTING,
00029         CONNECTED,
00030         DISCONNECTED,
00031     };
00032 
00033     BlynkProtocol(Transp& transp)
00034         : conn(transp)
00035         , authkey(NULL)
00036         , redir_serv(NULL)
00037         , lastActivityIn(0)
00038         , lastActivityOut(0)
00039         , lastHeartbeat(0)
00040         , msgIdOut(0)
00041         , msgIdOutOverride(0)
00042         , nesting(0)
00043         , state(CONNECTING)
00044     {}
00045 
00046     bool connected () { return state == CONNECTED; }
00047 
00048     bool connect(uint32_t timeout = BLYNK_TIMEOUT_MS*3) {
00049         conn.disconnect();
00050         state = CONNECTING;
00051         millis_time_t started = BlynkMillis();
00052         while ((state != CONNECTED) &&
00053                (BlynkMillis() - started < timeout))
00054         {
00055             run();
00056         }
00057         return state == CONNECTED;
00058     }
00059 
00060     void disconnect() {
00061         conn.disconnect();
00062         state = DISCONNECTED;
00063         BLYNK_LOG1(BLYNK_F("Disconnected"));
00064     }
00065 
00066     bool run(bool avail = false);
00067 
00068     // TODO: Fixme
00069     void startSession() {
00070         conn.connect();
00071         state = CONNECTING;
00072         msgIdOut = 0;
00073         lastHeartbeat = lastActivityIn = lastActivityOut = (BlynkMillis() - 5000UL);
00074     }
00075 
00076     void sendCmd(uint8_t cmd, uint16_t id = 0, const void* data = NULL, size_t length = 0, const void* data2 = NULL, size_t length2 = 0);
00077 
00078 private:
00079 
00080     void internalReconnect() {
00081         state = CONNECTING;
00082         conn.disconnect();
00083         BlynkOnDisconnected();
00084     }
00085 
00086     int readHeader(BlynkHeader& hdr);
00087     uint16_t getNextMsgId();
00088 
00089 protected:
00090     void begin(const char* auth) {
00091         this->authkey = auth;
00092         lastHeartbeat = lastActivityIn = lastActivityOut = (BlynkMillis() - 5000UL);
00093 
00094 #if defined(BLYNK_NO_FANCY_LOGO)
00095         BLYNK_LOG1(BLYNK_F("Blynk v" BLYNK_VERSION " on " BLYNK_INFO_DEVICE));
00096 #elif defined(BLYNK_FANCY_LOGO_3D)
00097         BLYNK_LOG1(BLYNK_F("\n"
00098             "   ____     ___                      __\n"
00099             "  /\\  _`\\  /\\_ \\                    /\\ \\  _\n"
00100             "  \\ \\ \\_\\ \\\\//\\ \\    __  __     ___ \\ \\ \\/ \\\n"
00101             "   \\ \\  _ <  \\ \\ \\  /\\ \\/\\ \\  /' _ `\\\\ \\ , <\n"
00102             "    \\ \\ \\_\\ \\ \\_\\ \\_\\ \\ \\_\\ \\ /\\ \\/\\ \\\\ \\ \\\\`\\\n"
00103             "     \\ \\____/ /\\____\\\\/`____ \\\\ \\_\\ \\_\\\\ \\_\\\\_\\\n"
00104             "      \\/___/  \\/____/ `/___/\\ \\\\/_/\\/_/ \\/_//_/\n"
00105             "                         /\\___/\n"
00106             "                         \\/__/   " BLYNK_VERSION " on " BLYNK_INFO_DEVICE "\n"
00107         ));
00108 #else
00109         BLYNK_LOG1(BLYNK_F("\n"
00110             "    ___  __          __\n"
00111             "   / _ )/ /_ _____  / /__\n"
00112             "  / _  / / // / _ \\/  '_/\n"
00113             " /____/_/\\_, /_//_/_/\\_\\\n"
00114             "        /___/ v" BLYNK_VERSION " on " BLYNK_INFO_DEVICE "\n"
00115         ));
00116 #endif
00117     }
00118     bool processInput(void);
00119 
00120     Transp& conn;
00121 
00122 private:
00123     const char* authkey;
00124     char*       redir_serv;
00125     millis_time_t lastActivityIn;
00126     millis_time_t lastActivityOut;
00127     union {
00128         millis_time_t lastHeartbeat;
00129         millis_time_t lastLogin;
00130     };
00131     uint16_t msgIdOut;
00132     uint16_t msgIdOutOverride;
00133     uint8_t  nesting;
00134 protected:
00135     BlynkState state;
00136 };
00137 
00138 template <class Transp>
00139 bool BlynkProtocol<Transp>::run(bool avail)
00140 {
00141     BLYNK_RUN_YIELD();
00142 
00143     if (state == DISCONNECTED) {
00144         return false;
00145     }
00146 
00147     // Detect nesting
00148     BlynkHelperAutoInc guard(nesting);
00149     if (msgIdOutOverride || nesting > 2) {
00150 #ifdef BLYNK_DEBUG_ALL
00151       BLYNK_LOG1(BLYNK_F("Nested run() skipped"));
00152 #endif
00153       return true;
00154     }
00155 
00156     if (conn.connected()) {
00157         while (avail || conn.available() > 0) {
00158             //BLYNK_LOG2(BLYNK_F("Available: "), conn.available());
00159             //const unsigned long t = micros();
00160             if (!processInput()) {
00161                 conn.disconnect();
00162 // TODO: Only when in direct mode?
00163 #ifdef BLYNK_USE_DIRECT_CONNECT
00164                 state = CONNECTING;
00165 #endif
00166                 BlynkOnDisconnected();
00167                 return false;
00168             }
00169             avail = false;
00170             //BLYNK_LOG2(BLYNK_F("Proc time: "), micros() - t);
00171         }
00172     }
00173 
00174     const millis_time_t t = BlynkMillis();
00175 
00176     // Update connection status after running commands
00177     const bool tconn = conn.connected();
00178 
00179     if (state == CONNECTED) {
00180         if (!tconn) {
00181             lastHeartbeat = t;
00182             internalReconnect();
00183             return false;
00184         }
00185 
00186         if (t - lastActivityIn > (1000UL * BLYNK_HEARTBEAT + BLYNK_TIMEOUT_MS*3)) {
00187 #ifdef BLYNK_DEBUG
00188             BLYNK_LOG6(BLYNK_F("Heartbeat timeout: "), t, BLYNK_F(", "), lastActivityIn, BLYNK_F(", "), lastHeartbeat);
00189 #else
00190             BLYNK_LOG1(BLYNK_F("Heartbeat timeout"));
00191 #endif
00192             internalReconnect();
00193             return false;
00194         } else if ((t - lastActivityIn  > 1000UL * BLYNK_HEARTBEAT ||
00195                     t - lastActivityOut > 1000UL * BLYNK_HEARTBEAT) &&
00196                     t - lastHeartbeat   > BLYNK_TIMEOUT_MS)
00197         {
00198             // Send ping if we didn't either send or receive something
00199             // for BLYNK_HEARTBEAT seconds
00200             sendCmd(BLYNK_CMD_PING);
00201             lastHeartbeat = t;
00202         }
00203     } else if (state == CONNECTING) {
00204 #ifdef BLYNK_USE_DIRECT_CONNECT
00205         if (!tconn)
00206             conn.connect();
00207 #else
00208         if (tconn && (t - lastLogin > BLYNK_TIMEOUT_MS)) {
00209             BLYNK_LOG1(BLYNK_F("Login timeout"));
00210             conn.disconnect();
00211             state = CONNECTING;
00212             return false;
00213         } else if (!tconn && (t - lastLogin > 5000UL)) {
00214             conn.disconnect();
00215             if (!conn.connect()) {
00216                 lastLogin = t;
00217                 return false;
00218             }
00219 
00220             msgIdOut = 1;
00221             sendCmd(BLYNK_CMD_LOGIN, 1, authkey, strlen(authkey));
00222             lastLogin = lastActivityOut;
00223             return true;
00224         }
00225 #endif
00226     }
00227     return true;
00228 }
00229 
00230 template <class Transp>
00231 BLYNK_FORCE_INLINE
00232 bool BlynkProtocol<Transp>::processInput(void)
00233 {
00234     BlynkHeader hdr;
00235     const int ret = readHeader(hdr);
00236 
00237     if (ret == 0) {
00238         return true; // Considered OK (no data on input)
00239     }
00240 
00241     if (ret < 0 || hdr.msg_id == 0) {
00242 #ifdef BLYNK_DEBUG
00243         BLYNK_LOG2(BLYNK_F("Bad hdr len: "), ret);
00244 #endif
00245         return false;
00246     }
00247 
00248     if (hdr.type == BLYNK_CMD_RESPONSE) {
00249         lastActivityIn = BlynkMillis();
00250 
00251 #ifndef BLYNK_USE_DIRECT_CONNECT
00252         if (state == CONNECTING && (1 == hdr.msg_id)) {
00253             switch (hdr.length) {
00254             case BLYNK_SUCCESS:
00255             case BLYNK_ALREADY_REGISTERED:
00256                 BLYNK_LOG3(BLYNK_F("Ready (ping: "), lastActivityIn-lastHeartbeat, BLYNK_F("ms)."));
00257                 lastHeartbeat = lastActivityIn;
00258                 state = CONNECTED;
00259 #ifdef BLYNK_DEBUG
00260                 if (size_t ram = BlynkFreeRam()) {
00261                     BLYNK_LOG2(BLYNK_F("Free RAM: "), ram);
00262                 }
00263 #endif
00264                 this->sendInfo();
00265                 BLYNK_RUN_YIELD();
00266                 BlynkOnConnected();
00267                 return true;
00268             case BLYNK_INVALID_TOKEN:
00269                 BLYNK_LOG1(BLYNK_F("Invalid auth token"));
00270                 break;
00271             default:
00272                 BLYNK_LOG2(BLYNK_F("Connect failed. code: "), hdr.length);
00273             }
00274             return false;
00275         }
00276         if (BLYNK_NOT_AUTHENTICATED == hdr.length) {
00277             return false;
00278         }
00279 #endif
00280         // TODO: return code may indicate App presence
00281         return true;
00282     }
00283 
00284     if (hdr.length > BLYNK_MAX_READBYTES) {
00285         BLYNK_LOG2(BLYNK_F("Packet too big: "), hdr.length);
00286         // TODO: Flush
00287         internalReconnect();
00288         return true;
00289     }
00290 
00291     uint8_t inputBuffer[hdr.length+1]; // Add 1 to zero-terminate
00292     if (hdr.length != conn.read(inputBuffer, hdr.length)) {
00293 #ifdef BLYNK_DEBUG
00294         BLYNK_LOG1(BLYNK_F("Can't read body"));
00295 #endif
00296         return false;
00297     }
00298     inputBuffer[hdr.length] = '\0';
00299 
00300     BLYNK_DBG_DUMP(">", inputBuffer, hdr.length);
00301 
00302     lastActivityIn = BlynkMillis();
00303 
00304     switch (hdr.type)
00305     {
00306     case BLYNK_CMD_LOGIN: {
00307 #ifdef BLYNK_USE_DIRECT_CONNECT
00308         if (strncmp(authkey, (char*)inputBuffer, 32)) {
00309             BLYNK_LOG1(BLYNK_F("Invalid token"));
00310             sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_INVALID_TOKEN);
00311             break;
00312         }
00313 #endif
00314         if (state == CONNECTING) {
00315             BLYNK_LOG1(BLYNK_F("Ready"));
00316             state = CONNECTED;
00317 #ifdef BLYNK_DEBUG
00318             if (size_t ram = BlynkFreeRam()) {
00319                 BLYNK_LOG2(BLYNK_F("Free RAM: "), ram);
00320             }
00321 #endif
00322             this->sendInfo();
00323             BLYNK_RUN_YIELD();
00324             BlynkOnConnected();
00325         }
00326         sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS);
00327     } break;
00328     case BLYNK_CMD_PING: {
00329         sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS);
00330     } break;
00331     case BLYNK_CMD_REDIRECT: {
00332         if (!redir_serv) {
00333              redir_serv = (char*)malloc(32);
00334         }
00335         BlynkParam param(inputBuffer, hdr.length);
00336         uint16_t redir_port = BLYNK_DEFAULT_PORT; // TODO: Fixit
00337 
00338         BlynkParam::iterator it = param.begin();
00339         if (it >= param.end())
00340             return false;
00341         strncpy(redir_serv, it.asStr(), 32);
00342         if (++it < param.end())
00343             redir_port = it.asLong();
00344         BLYNK_LOG4(BLYNK_F("Redirecting to "), redir_serv, ':', redir_port);
00345         conn.disconnect();
00346         conn.begin(redir_serv, redir_port);
00347         state = CONNECTING;
00348         lastHeartbeat = lastActivityIn = lastActivityOut = (BlynkMillis() - 5000UL);
00349     } break;
00350     case BLYNK_CMD_HARDWARE:
00351     case BLYNK_CMD_BRIDGE: {
00352         msgIdOutOverride = hdr.msg_id;
00353         this->processCmd(inputBuffer, hdr.length);
00354         msgIdOutOverride = 0;
00355     } break;
00356     case BLYNK_CMD_INTERNAL: {
00357         BlynkReq req = { 0 };
00358         BlynkParam param(inputBuffer, hdr.length);
00359         BlynkParam::iterator it = param.begin();
00360         if (it >= param.end())
00361             return true;
00362 
00363         uint32_t cmd32;
00364         memcpy(&cmd32, it.asStr(), sizeof(cmd32));
00365 
00366         ++it;
00367         char* start = (char*)(it).asStr();
00368         unsigned length = hdr.length - (start - (char*)inputBuffer);
00369         BlynkParam param2(start, length);
00370 
00371         switch (cmd32) {
00372         case BLYNK_INT_RTC:  BlynkWidgetWriteInternalPinRTC(req, param2);    break;
00373         case BLYNK_INT_OTA:  BlynkWidgetWriteInternalPinOTA(req, param2);    break;
00374         case BLYNK_INT_ACON: BlynkWidgetWriteInternalPinACON(req, param2);   break;
00375         case BLYNK_INT_ADIS: BlynkWidgetWriteInternalPinADIS(req, param2);   break;
00376 #ifdef BLYNK_DEBUG
00377         default:             BLYNK_LOG2(BLYNK_F("Invalid internal cmd:"), param.asStr());
00378 #endif
00379         }
00380     } break;
00381     case BLYNK_CMD_DEBUG_PRINT: {
00382         if (hdr.length) {
00383             BLYNK_LOG2(BLYNK_F("Server: "), (char*)inputBuffer);
00384         }
00385     } break;
00386     default: {
00387 #ifdef BLYNK_DEBUG
00388         BLYNK_LOG2(BLYNK_F("Invalid header type: "), hdr.type);
00389 #endif
00390         // TODO: Flush
00391         internalReconnect();
00392     } break;
00393     }
00394 
00395     return true;
00396 }
00397 
00398 template <class Transp>
00399 int BlynkProtocol<Transp>::readHeader(BlynkHeader& hdr)
00400 {
00401     size_t rlen = conn.read(&hdr, sizeof(hdr));
00402     if (rlen == 0) {
00403         return 0;
00404     }
00405 
00406     if (sizeof(hdr) != rlen) {
00407         return -1;
00408     }
00409 
00410     BLYNK_DBG_DUMP(">", &hdr, sizeof(BlynkHeader));
00411 
00412     hdr.msg_id = ntohs(hdr.msg_id);
00413     hdr.length = ntohs(hdr.length);
00414 
00415     return rlen;
00416 }
00417 
00418 #ifndef BLYNK_SEND_THROTTLE
00419 #define BLYNK_SEND_THROTTLE 0
00420 #endif
00421 
00422 #ifndef BLYNK_SEND_CHUNK
00423 #define BLYNK_SEND_CHUNK 1024 // Just a big number
00424 #endif
00425 
00426 template <class Transp>
00427 void BlynkProtocol<Transp>::sendCmd(uint8_t cmd, uint16_t id, const void* data, size_t length, const void* data2, size_t length2)
00428 {
00429     if (!conn.connected() || (cmd != BLYNK_CMD_RESPONSE && cmd != BLYNK_CMD_PING && cmd != BLYNK_CMD_LOGIN && state != CONNECTED) ) {
00430 #ifdef BLYNK_DEBUG_ALL
00431         BLYNK_LOG2(BLYNK_F("Cmd skipped:"), cmd);
00432 #endif
00433         return;
00434     }
00435 
00436     if (0 == id) {
00437         id = getNextMsgId();
00438     }
00439 
00440 #if defined(BLYNK_MSG_LIMIT) && BLYNK_MSG_LIMIT > 0
00441     if (cmd >= BLYNK_CMD_TWEET && cmd <= BLYNK_CMD_HARDWARE) {
00442         const millis_time_t allowed_time = BlynkMax(lastActivityOut, lastActivityIn) + 1000/BLYNK_MSG_LIMIT;
00443         long wait_time = allowed_time - BlynkMillis();
00444         if (wait_time >= 0) {
00445 #ifdef BLYNK_DEBUG_ALL
00446             BLYNK_LOG2(BLYNK_F("Waiting:"), wait_time);
00447 #endif
00448             while (wait_time >= 0) {
00449                 run();
00450                 wait_time = allowed_time - BlynkMillis();
00451             }
00452         } else if (nesting == 0) {
00453             run();
00454         }
00455     }
00456 #endif
00457 
00458     const size_t full_length = (sizeof(BlynkHeader)) +
00459                                (data  ? length  : 0) +
00460                                (data2 ? length2 : 0);
00461 
00462 #if defined(BLYNK_SEND_ATOMIC) || defined(ESP8266) || defined(ESP32) || defined(SPARK) || defined(PARTICLE) || defined(ENERGIA)
00463     // Those have more RAM and like single write at a time...
00464 
00465     uint8_t buff[full_length];
00466 
00467     BlynkHeader* hdr = (BlynkHeader*)buff;
00468     hdr->type = cmd;
00469     hdr->msg_id = htons(id);
00470     hdr->length = htons(length+length2);
00471 
00472     size_t pos = sizeof(BlynkHeader);
00473     if (data && length) {
00474         memcpy(buff + pos, data, length);
00475         pos += length;
00476     }
00477     if (data2 && length2) {
00478         memcpy(buff + pos, data2, length2);
00479     }
00480 
00481     size_t wlen = 0;
00482     while (wlen < full_length) {
00483         const size_t chunk = BlynkMin(size_t(BLYNK_SEND_CHUNK), full_length - wlen);
00484         BLYNK_DBG_DUMP("<", buff + wlen, chunk);
00485         const size_t w = conn.write(buff + wlen, chunk);
00486         BlynkDelay(BLYNK_SEND_THROTTLE);
00487         if (w == 0) {
00488 #ifdef BLYNK_DEBUG
00489             BLYNK_LOG1(BLYNK_F("Cmd error"));
00490 #endif
00491             conn.disconnect();
00492             state = CONNECTING;
00493             BlynkOnDisconnected();
00494             return;
00495         }
00496         wlen += w;
00497     }
00498 
00499 #else
00500 
00501     BlynkHeader hdr;
00502     hdr.type = cmd;
00503     hdr.msg_id = htons(id);
00504     hdr.length = htons(length+length2);
00505 
00506     BLYNK_DBG_DUMP("<", &hdr, sizeof(hdr));
00507     size_t wlen = conn.write(&hdr, sizeof(hdr));
00508     BlynkDelay(BLYNK_SEND_THROTTLE);
00509 
00510     if (cmd != BLYNK_CMD_RESPONSE) {
00511         if (length) {
00512             BLYNK_DBG_DUMP("<", data, length);
00513             wlen += conn.write(data, length);
00514             BlynkDelay(BLYNK_SEND_THROTTLE);
00515         }
00516         if (length2) {
00517             BLYNK_DBG_DUMP("<", data2, length2);
00518             wlen += conn.write(data2, length2);
00519             BlynkDelay(BLYNK_SEND_THROTTLE);
00520         }
00521     }
00522 
00523 #endif
00524 
00525     if (wlen != full_length) {
00526 #ifdef BLYNK_DEBUG
00527         BLYNK_LOG4(BLYNK_F("Sent "), wlen, '/', full_length);
00528 #endif
00529         internalReconnect();
00530         return;
00531     }
00532 
00533     lastActivityOut = BlynkMillis();
00534 
00535 }
00536 
00537 template <class Transp>
00538 uint16_t BlynkProtocol<Transp>::getNextMsgId()
00539 {
00540     if (msgIdOutOverride != 0)
00541         return msgIdOutOverride;
00542     if (++msgIdOut == 0)
00543         msgIdOut = 1;
00544     return msgIdOut;
00545 }
00546 
00547 #endif