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
Diff: Blynk/BlynkProtocol.h
- Revision:
- 0:58b20b438383
- Child:
- 3:31e4b850b126
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Blynk/BlynkProtocol.h Sat May 07 08:02:50 2016 +0000 @@ -0,0 +1,441 @@ +/** + * @file BlynkProtocol.h + * @author Volodymyr Shymanskyy + * @license This project is released under the MIT License (MIT) + * @copyright Copyright (c) 2015 Volodymyr Shymanskyy + * @date Jan 2015 + * @brief Blynk protocol implementation + * + */ + +#ifndef BlynkProtocol_h +#define BlynkProtocol_h + +#include <string.h> +#include <stdlib.h> +#include <Blynk/BlynkDebug.h> +#include <Blynk/BlynkProtocolDefs.h> +#include <Blynk/BlynkApi.h> +#include <utility/BlynkUtility.h> + +template <class Transp> +class BlynkProtocol + : public BlynkApi< BlynkProtocol<Transp> > +{ + friend class BlynkApi< BlynkProtocol<Transp> >; +public: + enum BlynkState { + CONNECTING, + CONNECTED, + DISCONNECTED, + }; + + BlynkProtocol(Transp& transp) + : conn(transp) + , authkey(NULL) + , lastActivityIn(0) + , lastActivityOut(0) + , lastHeartbeat(0) +#ifdef BLYNK_MSG_LIMIT + , deltaCmd(0) +#endif + , currentMsgId(0) + , state(CONNECTING) + {} + + bool connected() { return state == CONNECTED; } + + bool connect(uint32_t timeout = BLYNK_TIMEOUT_MS*3) { + conn.disconnect(); + state = CONNECTING; + millis_time_t started = this->getMillis(); + while ((state != CONNECTED) && + (this->getMillis() - started < timeout)) + { + run(); + } + return state == CONNECTED; + } + + void disconnect() { + conn.disconnect(); + state = DISCONNECTED; + BLYNK_LOG1(BLYNK_F("Disconnected")); + } + + bool run(bool avail = false); + + void startSession() { + //TODO: conn.connect(); + state = CONNECTING; +#ifdef BLYNK_MSG_LIMIT + deltaCmd = 1000; +#endif + currentMsgId = 0; + lastHeartbeat = lastActivityIn = lastActivityOut = this->getMillis(); // TODO: - 5005UL + } + + 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); + +private: + int readHeader(BlynkHeader& hdr); + uint16_t getNextMsgId(); + +protected: + void begin(const char* auth) { + BLYNK_LOG1(BLYNK_F("Blynk v" BLYNK_VERSION " on " BLYNK_INFO_DEVICE)); + this->authkey = auth; + } + bool processInput(void); + + Transp& conn; + +private: + const char* authkey; + millis_time_t lastActivityIn; + millis_time_t lastActivityOut; + union { + millis_time_t lastHeartbeat; + millis_time_t lastLogin; + }; +#ifdef BLYNK_MSG_LIMIT + millis_time_t deltaCmd; +#endif + uint16_t currentMsgId; + BlynkState state; +}; + +template <class Transp> +bool BlynkProtocol<Transp>::run(bool avail) +{ +#if !defined(BLYNK_NO_YIELD) + yield(); +#endif + + if (state == DISCONNECTED) { + return false; + } + + const bool tconn = conn.connected(); + + if (tconn) { + if (avail || conn.available() > 0) { + //BLYNK_LOG2(BLYNK_F("Available: "), conn.available()); + //const unsigned long t = micros(); + if (!processInput()) { + conn.disconnect(); +// TODO: Only when in direct mode? +#ifdef BLYNK_USE_DIRECT_CONNECT + state = CONNECTING; +#endif + //BlynkOnDisconnected(); + return false; + } + //BLYNK_LOG2(BLYNK_F("Proc time: "), micros() - t); + } + } + + const millis_time_t t = this->getMillis(); + + if (state == CONNECTED) { + if (!tconn) { + state = CONNECTING; + lastHeartbeat = t; + //BlynkOnDisconnected(); + return false; + } + + if (t - lastActivityIn > (1000UL * BLYNK_HEARTBEAT + BLYNK_TIMEOUT_MS*3)) { +#ifdef BLYNK_DEBUG + BLYNK_LOG6(BLYNK_F("Heartbeat timeout: "), t, BLYNK_F(", "), lastActivityIn, BLYNK_F(", "), lastHeartbeat); +#else + BLYNK_LOG1(BLYNK_F("Heartbeat timeout")); +#endif + conn.disconnect(); + state = CONNECTING; + //BlynkOnDisconnected(); + return false; + } else if ((t - lastActivityIn > 1000UL * BLYNK_HEARTBEAT || + t - lastActivityOut > 1000UL * BLYNK_HEARTBEAT) && + t - lastHeartbeat > BLYNK_TIMEOUT_MS) + { + // Send ping if we didn't either send or receive something + // for BLYNK_HEARTBEAT seconds + sendCmd(BLYNK_CMD_PING); + lastHeartbeat = t; + } +#ifndef BLYNK_USE_DIRECT_CONNECT + } else if (state == CONNECTING) { + if (tconn && (t - lastLogin > BLYNK_TIMEOUT_MS)) { + BLYNK_LOG1(BLYNK_F("Login timeout")); + conn.disconnect(); + state = CONNECTING; + return false; + } else if (!tconn && (t - lastLogin > 5000UL)) { + conn.disconnect(); + if (!conn.connect()) { + lastLogin = t; + return false; + } + +#ifdef BLYNK_MSG_LIMIT + deltaCmd = 1000; +#endif + sendCmd(BLYNK_CMD_LOGIN, 1, authkey, strlen(authkey)); + lastLogin = lastActivityOut; + return true; + } +#endif + } + return true; +} + +template <class Transp> +BLYNK_FORCE_INLINE +bool BlynkProtocol<Transp>::processInput(void) +{ + BlynkHeader hdr; + const int ret = readHeader(hdr); + + if (ret == 0) { + return true; // Considered OK (no data on input) + } + + if (ret < 0 || hdr.msg_id == 0) { +#ifdef BLYNK_DEBUG + BLYNK_LOG1(BLYNK_F("Wrong header on input")); +#endif + return false; + } + + if (hdr.type == BLYNK_CMD_RESPONSE) { + lastActivityIn = this->getMillis(); + +#ifndef BLYNK_USE_DIRECT_CONNECT + if (state == CONNECTING && (1 == hdr.msg_id)) { + switch (hdr.length) { + case BLYNK_SUCCESS: + case BLYNK_ALREADY_LOGGED_IN: + BLYNK_LOG3(BLYNK_F("Ready (ping: "), lastActivityIn-lastHeartbeat, BLYNK_F("ms).")); + lastHeartbeat = lastActivityIn; + state = CONNECTED; + this->sendInfo(); +#if !defined(BLYNK_NO_YIELD) + yield(); +#endif + BlynkOnConnected(); + return true; + case BLYNK_INVALID_TOKEN: + BLYNK_LOG1(BLYNK_F("Invalid auth token")); + break; + default: + BLYNK_LOG2(BLYNK_F("Connect failed. code: "), hdr.length); + } + return false; + } + if (BLYNK_NOT_AUTHENTICATED == hdr.length) { + return false; + } +#endif + // TODO: return code may indicate App presence + return true; + } + + if (hdr.length > BLYNK_MAX_READBYTES) { +#ifdef BLYNK_DEBUG + BLYNK_LOG2(BLYNK_F("Packet too big: "), hdr.length); +#endif + return false; + } + + uint8_t inputBuffer[hdr.length+1]; // Add 1 to zero-terminate + if (hdr.length != conn.read(inputBuffer, hdr.length)) { +#ifdef DEBUG + BLYNK_LOG1(BLYNK_F("Can't read body")); +#endif + return false; + } + inputBuffer[hdr.length] = '\0'; + + BLYNK_DBG_DUMP(">", inputBuffer, hdr.length); + + lastActivityIn = this->getMillis(); + + switch (hdr.type) + { +#ifdef BLYNK_USE_DIRECT_CONNECT + case BLYNK_CMD_LOGIN: { + if (!strncmp(authkey, (char*)inputBuffer, 32)) { + state = CONNECTED; + sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS); + this->sendInfo(); + } else { + sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_INVALID_TOKEN); + } + } break; +#endif + case BLYNK_CMD_PING: { + sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS); + } break; + case BLYNK_CMD_HARDWARE: + case BLYNK_CMD_BRIDGE: { + currentMsgId = hdr.msg_id; + this->processCmd(inputBuffer, hdr.length); + currentMsgId = 0; + } break; + default: { +#ifdef BLYNK_DEBUG + BLYNK_LOG2(BLYNK_F("Invalid header type: "), hdr.type); +#endif + } break; + } + + return true; +} + +template <class Transp> +int BlynkProtocol<Transp>::readHeader(BlynkHeader& hdr) +{ + size_t rlen = conn.read(&hdr, sizeof(hdr)); + if (rlen == 0) { + return 0; + } + + if (sizeof(hdr) != rlen) { + return -1; + } + hdr.msg_id = ntohs(hdr.msg_id); + hdr.length = ntohs(hdr.length); + + BLYNK_DBG_DUMP(">", &hdr, sizeof(BlynkHeader)); + return rlen; +} + +#ifndef BLYNK_SEND_THROTTLE +#define BLYNK_SEND_THROTTLE 0 +#endif + +#ifndef BLYNK_SEND_CHUNK +#define BLYNK_SEND_CHUNK 1024 // Just a big number +#endif + +template <class Transp> +void BlynkProtocol<Transp>::sendCmd(uint8_t cmd, uint16_t id, const void* data, size_t length, const void* data2, size_t length2) +{ + if (0 == id) { + id = getNextMsgId(); + } + + if (!conn.connected() || (cmd != BLYNK_CMD_RESPONSE && cmd != BLYNK_CMD_PING && cmd != BLYNK_CMD_LOGIN && state != CONNECTED) ) { +#ifdef BLYNK_DEBUG + BLYNK_LOG2(BLYNK_F("Cmd skipped:"), cmd); +#endif + return; + } + + const int full_length = (sizeof(BlynkHeader)) + + (data ? length : 0) + + (data2 ? length2 : 0); + +#if defined(BLYNK_SEND_ATOMIC) || defined(ESP8266) || defined(SPARK) || defined(PARTICLE) || defined(ENERGIA) + // Those have more RAM and like single write at a time... + + uint8_t buff[full_length]; + + BlynkHeader* hdr = (BlynkHeader*)buff; + hdr->type = cmd; + hdr->msg_id = htons(id); + hdr->length = htons(length+length2); + + size_t pos = sizeof(BlynkHeader); + if (data && length) { + memcpy(buff + pos, data, length); + pos += length; + } + if (data2 && length2) { + memcpy(buff + pos, data2, length2); + } + + size_t wlen = 0; + while (wlen < full_length) { + const size_t chunk = BlynkMin(size_t(BLYNK_SEND_CHUNK), full_length - wlen); + BLYNK_DBG_DUMP("<", buff + wlen, chunk); + const size_t w = conn.write(buff + wlen, chunk); + delay(BLYNK_SEND_THROTTLE); + if (w == 0) { +#ifdef BLYNK_DEBUG + BLYNK_LOG1(BLYNK_F("Cmd error")); +#endif + conn.disconnect(); + state = CONNECTING; + //BlynkOnDisconnected(); + return; + } + wlen += w; + } + +#else + + BlynkHeader hdr; + hdr.type = cmd; + hdr.msg_id = htons(id); + hdr.length = htons(length+length2); + + BLYNK_DBG_DUMP("<", &hdr, sizeof(hdr)); + size_t wlen = conn.write(&hdr, sizeof(hdr)); + delay(BLYNK_SEND_THROTTLE); + + if (cmd != BLYNK_CMD_RESPONSE) { + if (length) { + BLYNK_DBG_DUMP("<", data, length); + wlen += conn.write(data, length); + delay(BLYNK_SEND_THROTTLE); + } + if (length2) { + BLYNK_DBG_DUMP("<", data2, length2); + wlen += conn.write(data2, length2); + delay(BLYNK_SEND_THROTTLE); + } + } + +#endif + + if (wlen != full_length) { +#ifdef BLYNK_DEBUG + BLYNK_LOG4(BLYNK_F("Sent "), wlen, '/', full_length); +#endif + conn.disconnect(); + state = CONNECTING; + //BlynkOnDisconnected(); + return; + } + +#if defined BLYNK_MSG_LIMIT && BLYNK_MSG_LIMIT > 0 + const millis_time_t ts = this->getMillis(); + BlynkAverageSample<32>(deltaCmd, ts - lastActivityOut); + lastActivityOut = ts; + //BLYNK_LOG2(BLYNK_F("Delta: "), deltaCmd); + if (deltaCmd < (1000/BLYNK_MSG_LIMIT)) { + BLYNK_LOG_TROUBLE(BLYNK_F("flood-error")); + conn.disconnect(); + state = CONNECTING; + //BlynkOnDisconnected(); + } +#else + lastActivityOut = this->getMillis(); +#endif + +} + +template <class Transp> +uint16_t BlynkProtocol<Transp>::getNextMsgId() +{ + static uint16_t last = 0; + if (currentMsgId != 0) + return currentMsgId; + if (++last == 0) + last = 1; + return last; +} + +#endif