Work on LPC824. 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:   ESP8266BlynkWeatherStation TEST123

Fork of Blynk by Volodymyr Shymanskyy

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         , lastActivityIn(0)
00037         , lastActivityOut(0)
00038         , lastHeartbeat(0)
00039 #ifdef BLYNK_MSG_LIMIT
00040         , deltaCmd(0)
00041 #endif
00042         , currentMsgId(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 = this->getMillis();
00052         while ((state != CONNECTED) &&
00053                (this->getMillis() - 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     void startSession() {
00069         //TODO: conn.connect();
00070         state = CONNECTING;
00071 #ifdef BLYNK_MSG_LIMIT
00072         deltaCmd = 1000;
00073 #endif
00074         currentMsgId = 0;
00075         lastHeartbeat = lastActivityIn = lastActivityOut = this->getMillis(); // TODO: - 5005UL
00076     }
00077 
00078     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);
00079 
00080 private:
00081     int readHeader(BlynkHeader& hdr);
00082     uint16_t getNextMsgId();
00083 
00084 protected:
00085     void begin(const char* auth) {
00086         BLYNK_LOG1(BLYNK_F("Blynk v" BLYNK_VERSION " on " BLYNK_INFO_DEVICE));
00087         this->authkey = auth;
00088     }
00089     bool processInput(void);
00090 
00091     Transp& conn;
00092 
00093 private:
00094     const char* authkey;
00095     millis_time_t lastActivityIn;
00096     millis_time_t lastActivityOut;
00097     union {
00098         millis_time_t lastHeartbeat;
00099         millis_time_t lastLogin;
00100     };
00101 #ifdef BLYNK_MSG_LIMIT
00102     millis_time_t deltaCmd;
00103 #endif
00104     uint16_t currentMsgId;
00105     BlynkState state;
00106 };
00107 
00108 template <class Transp>
00109 bool BlynkProtocol<Transp>::run(bool avail)
00110 {
00111 #if !defined(BLYNK_NO_YIELD)
00112     yield();
00113 #endif
00114 
00115     if (state == DISCONNECTED) {
00116         return false;
00117     }
00118 
00119     const bool tconn = conn.connected();
00120 
00121     if (tconn) {
00122         if (avail || conn.available() > 0) {
00123             //BLYNK_LOG2(BLYNK_F("Available: "), conn.available());
00124             //const unsigned long t = micros();
00125             if (!processInput()) {
00126                 conn.disconnect();
00127 // TODO: Only when in direct mode?
00128 #ifdef BLYNK_USE_DIRECT_CONNECT
00129                 state = CONNECTING;
00130 #endif
00131                 //BlynkOnDisconnected();
00132                 return false;
00133             }
00134             //BLYNK_LOG2(BLYNK_F("Proc time: "), micros() - t);
00135         }
00136     }
00137 
00138     const millis_time_t t = this->getMillis();
00139 
00140     if (state == CONNECTED) {
00141         if (!tconn) {
00142             state = CONNECTING;
00143             lastHeartbeat = t;
00144             //BlynkOnDisconnected();
00145             return false;
00146         }
00147 
00148         if (t - lastActivityIn > (1000UL * BLYNK_HEARTBEAT + BLYNK_TIMEOUT_MS*3)) {
00149 #ifdef BLYNK_DEBUG
00150             BLYNK_LOG6(BLYNK_F("Heartbeat timeout: "), t, BLYNK_F(", "), lastActivityIn, BLYNK_F(", "), lastHeartbeat);
00151 #else
00152             BLYNK_LOG1(BLYNK_F("Heartbeat timeout"));
00153 #endif
00154             conn.disconnect();
00155             state = CONNECTING;
00156             //BlynkOnDisconnected();
00157             return false;
00158         } else if ((t - lastActivityIn  > 1000UL * BLYNK_HEARTBEAT ||
00159                     t - lastActivityOut > 1000UL * BLYNK_HEARTBEAT) &&
00160                     t - lastHeartbeat   > BLYNK_TIMEOUT_MS)
00161         {
00162             // Send ping if we didn't either send or receive something
00163             // for BLYNK_HEARTBEAT seconds
00164             sendCmd(BLYNK_CMD_PING);
00165             lastHeartbeat = t;
00166         }
00167 #ifndef BLYNK_USE_DIRECT_CONNECT
00168     } else if (state == CONNECTING) {
00169         if (tconn && (t - lastLogin > BLYNK_TIMEOUT_MS)) {
00170             BLYNK_LOG1(BLYNK_F("Login timeout"));
00171             conn.disconnect();
00172             state = CONNECTING;
00173             return false;
00174         } else if (!tconn && (t - lastLogin > 5000UL)) {
00175             conn.disconnect();
00176             if (!conn.connect()) {
00177                 lastLogin = t;
00178                 return false;
00179             }
00180 
00181 #ifdef BLYNK_MSG_LIMIT
00182             deltaCmd = 1000;
00183 #endif
00184             sendCmd(BLYNK_CMD_LOGIN, 1, authkey, strlen(authkey));
00185             lastLogin = lastActivityOut;
00186             return true;
00187         }
00188 #endif
00189     }
00190     return true;
00191 }
00192 
00193 template <class Transp>
00194 BLYNK_FORCE_INLINE
00195 bool BlynkProtocol<Transp>::processInput(void)
00196 {
00197     BlynkHeader hdr;
00198     const int ret = readHeader(hdr);
00199 
00200     if (ret == 0) {
00201         return true; // Considered OK (no data on input)
00202     }
00203 
00204     if (ret < 0 || hdr.msg_id == 0) {
00205 #ifdef BLYNK_DEBUG
00206         BLYNK_LOG1(BLYNK_F("Wrong header on input"));
00207 #endif
00208         return false;
00209     }
00210 
00211     if (hdr.type == BLYNK_CMD_RESPONSE) {
00212         lastActivityIn = this->getMillis();
00213 
00214 #ifndef BLYNK_USE_DIRECT_CONNECT
00215         if (state == CONNECTING && (1 == hdr.msg_id)) {
00216             switch (hdr.length) {
00217             case BLYNK_SUCCESS:
00218             case BLYNK_ALREADY_LOGGED_IN:
00219                 BLYNK_LOG3(BLYNK_F("Ready (ping: "), lastActivityIn-lastHeartbeat, BLYNK_F("ms)."));
00220                 lastHeartbeat = lastActivityIn;
00221                 state = CONNECTED;
00222                 this->sendInfo();
00223 #if !defined(BLYNK_NO_YIELD)
00224                 yield();
00225 #endif
00226                 BlynkOnConnected();
00227                 return true;
00228             case BLYNK_INVALID_TOKEN:
00229                 BLYNK_LOG1(BLYNK_F("Invalid auth token"));
00230                 break;
00231             default:
00232                 BLYNK_LOG2(BLYNK_F("Connect failed. code: "), hdr.length);
00233             }
00234             return false;
00235         }
00236         if (BLYNK_NOT_AUTHENTICATED == hdr.length) {
00237             return false;
00238         }
00239 #endif
00240         // TODO: return code may indicate App presence
00241         return true;
00242     }
00243 
00244     if (hdr.length > BLYNK_MAX_READBYTES) {
00245 #ifdef BLYNK_DEBUG
00246         BLYNK_LOG2(BLYNK_F("Packet too big: "), hdr.length);
00247 #endif
00248         return false;
00249     }
00250 
00251     uint8_t inputBuffer[hdr.length+1]; // Add 1 to zero-terminate
00252     if (hdr.length != conn.read(inputBuffer, hdr.length)) {
00253 #ifdef DEBUG
00254         BLYNK_LOG1(BLYNK_F("Can't read body"));
00255 #endif
00256         return false;
00257     }
00258     inputBuffer[hdr.length] = '\0';
00259 
00260     BLYNK_DBG_DUMP(">", inputBuffer, hdr.length);
00261 
00262     lastActivityIn = this->getMillis();
00263 
00264     switch (hdr.type)
00265     {
00266 #ifdef BLYNK_USE_DIRECT_CONNECT
00267     case BLYNK_CMD_LOGIN: {
00268         if (!strncmp(authkey, (char*)inputBuffer, 32)) {
00269             state = CONNECTED;
00270             sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS);
00271             this->sendInfo();
00272         } else {
00273             sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_INVALID_TOKEN);
00274         }
00275     } break;
00276 #endif
00277     case BLYNK_CMD_PING: {
00278         sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS);
00279     } break;
00280     case BLYNK_CMD_HARDWARE:
00281     case BLYNK_CMD_BRIDGE: {
00282         currentMsgId = hdr.msg_id;
00283         this->processCmd(inputBuffer, hdr.length);
00284         currentMsgId = 0;
00285     } break;
00286     default: {
00287 #ifdef BLYNK_DEBUG
00288         BLYNK_LOG2(BLYNK_F("Invalid header type: "), hdr.type);
00289 #endif
00290     } break;
00291     }
00292 
00293     return true;
00294 }
00295 
00296 template <class Transp>
00297 int BlynkProtocol<Transp>::readHeader(BlynkHeader& hdr)
00298 {
00299     size_t rlen = conn.read(&hdr, sizeof(hdr));
00300     if (rlen == 0) {
00301         return 0;
00302     }
00303 
00304     if (sizeof(hdr) != rlen) {
00305         return -1;
00306     }
00307     hdr.msg_id = ntohs(hdr.msg_id);
00308     hdr.length = ntohs(hdr.length);
00309 
00310     BLYNK_DBG_DUMP(">", &hdr, sizeof(BlynkHeader));
00311     return rlen;
00312 }
00313 
00314 #ifndef BLYNK_SEND_THROTTLE
00315 #define BLYNK_SEND_THROTTLE 0
00316 #endif
00317 
00318 #ifndef BLYNK_SEND_CHUNK
00319 #define BLYNK_SEND_CHUNK 1024 // Just a big number
00320 #endif
00321 
00322 template <class Transp>
00323 void BlynkProtocol<Transp>::sendCmd(uint8_t cmd, uint16_t id, const void* data, size_t length, const void* data2, size_t length2)
00324 {
00325     if (0 == id) {
00326         id = getNextMsgId();
00327     }
00328 
00329     if (!conn.connected() || (cmd != BLYNK_CMD_RESPONSE && cmd != BLYNK_CMD_PING && cmd != BLYNK_CMD_LOGIN && state != CONNECTED) ) {
00330 #ifdef BLYNK_DEBUG
00331         BLYNK_LOG2(BLYNK_F("Cmd skipped:"), cmd);
00332 #endif
00333         return;
00334     }
00335 
00336     const int full_length = (sizeof(BlynkHeader)) +
00337                             (data  ? length  : 0) +
00338                             (data2 ? length2 : 0);
00339 
00340 #if defined(BLYNK_SEND_ATOMIC) || defined(ESP8266) || defined(SPARK) || defined(PARTICLE) || defined(ENERGIA)
00341     // Those have more RAM and like single write at a time...
00342 
00343     uint8_t buff[full_length];
00344 
00345     BlynkHeader* hdr = (BlynkHeader*)buff;
00346     hdr->type = cmd;
00347     hdr->msg_id = htons(id);
00348     hdr->length = htons(length+length2);
00349 
00350     size_t pos = sizeof(BlynkHeader);
00351     if (data && length) {
00352         memcpy(buff + pos, data, length);
00353         pos += length;
00354     }
00355     if (data2 && length2) {
00356         memcpy(buff + pos, data2, length2);
00357     }
00358 
00359     size_t wlen = 0;
00360     while (wlen < full_length) {
00361         const size_t chunk = BlynkMin(size_t(BLYNK_SEND_CHUNK), full_length - wlen);
00362         BLYNK_DBG_DUMP("<", buff + wlen, chunk);
00363         const size_t w = conn.write(buff + wlen, chunk);
00364         delay(BLYNK_SEND_THROTTLE);
00365         if (w == 0) {
00366 #ifdef BLYNK_DEBUG
00367             BLYNK_LOG1(BLYNK_F("Cmd error"));
00368 #endif
00369             conn.disconnect();
00370             state = CONNECTING;
00371             //BlynkOnDisconnected();
00372             return;
00373         }
00374         wlen += w;
00375     }
00376 
00377 #else
00378 
00379     BlynkHeader hdr;
00380     hdr.type = cmd;
00381     hdr.msg_id = htons(id);
00382     hdr.length = htons(length+length2);
00383 
00384     BLYNK_DBG_DUMP("<", &hdr, sizeof(hdr));
00385     size_t wlen = conn.write(&hdr, sizeof(hdr));
00386     delay(BLYNK_SEND_THROTTLE);
00387 
00388     if (cmd != BLYNK_CMD_RESPONSE) {
00389         if (length) {
00390             BLYNK_DBG_DUMP("<", data, length);
00391             wlen += conn.write(data, length);
00392             delay(BLYNK_SEND_THROTTLE);
00393         }
00394         if (length2) {
00395             BLYNK_DBG_DUMP("<", data2, length2);
00396             wlen += conn.write(data2, length2);
00397             delay(BLYNK_SEND_THROTTLE);
00398         }
00399     }
00400 
00401 #endif
00402 
00403     if (wlen != full_length) {
00404 #ifdef BLYNK_DEBUG
00405         BLYNK_LOG4(BLYNK_F("Sent "), wlen, '/', full_length);
00406 #endif
00407         conn.disconnect();
00408         state = CONNECTING;
00409         //BlynkOnDisconnected();
00410         return;
00411     }
00412 
00413 #if defined BLYNK_MSG_LIMIT && BLYNK_MSG_LIMIT > 0
00414     const millis_time_t ts = this->getMillis();
00415     BlynkAverageSample<32>(deltaCmd, ts - lastActivityOut);
00416     lastActivityOut = ts;
00417     //BLYNK_LOG2(BLYNK_F("Delta: "), deltaCmd);
00418     if (deltaCmd < (1000/BLYNK_MSG_LIMIT)) {
00419         BLYNK_LOG_TROUBLE(BLYNK_F("flood-error"));
00420         conn.disconnect();
00421         state = CONNECTING;
00422         //BlynkOnDisconnected();
00423     }
00424 #else
00425     lastActivityOut = this->getMillis();
00426 #endif
00427 
00428 }
00429 
00430 template <class Transp>
00431 uint16_t BlynkProtocol<Transp>::getNextMsgId()
00432 {
00433     static uint16_t last = 0;
00434     if (currentMsgId != 0)
00435         return currentMsgId;
00436     if (++last == 0)
00437         last = 1;
00438     return last;
00439 }
00440 
00441 #endif