BlynkProtocol.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /**
  2. * @file BlynkProtocol.h
  3. * @author Volodymyr Shymanskyy
  4. * @license This project is released under the MIT License (MIT)
  5. * @copyright Copyright (c) 2015 Volodymyr Shymanskyy
  6. * @date Jan 2015
  7. * @brief Blynk protocol implementation
  8. *
  9. * Modified May 2016 by myDevices.
  10. */
  11. #ifndef BlynkProtocol_h
  12. #define BlynkProtocol_h
  13. #include <string.h>
  14. #include <stdlib.h>
  15. #include <Blynk/BlynkDebug.h>
  16. #include <Blynk/BlynkProtocolDefs.h>
  17. #include <Blynk/BlynkApi.h>
  18. #include <utility/BlynkUtility.h>
  19. template <class Transp>
  20. class BlynkProtocol
  21. : public BlynkApi< BlynkProtocol<Transp> >
  22. {
  23. friend class BlynkApi< BlynkProtocol<Transp> >;
  24. public:
  25. enum BlynkState {
  26. CONNECTING,
  27. CONNECTED,
  28. DISCONNECTED,
  29. };
  30. BlynkProtocol(Transp& transp)
  31. : conn(transp), authkey(NULL)
  32. #ifdef BLYNK_USE_DIRECT_CONNECT
  33. , profile(NULL)
  34. #endif
  35. , lastActivityIn(0)
  36. , lastActivityOut(0)
  37. , lastHeartbeat(0)
  38. #ifdef BLYNK_MSG_LIMIT
  39. , deltaCmd(0)
  40. #endif
  41. , currentMsgId(0)
  42. , state(CONNECTING)
  43. {}
  44. bool connected() { return state == CONNECTED; }
  45. bool connect(uint32_t timeout = BLYNK_TIMEOUT_MS*3) {
  46. conn.disconnect();
  47. state = CONNECTING;
  48. uint32_t started = millis();
  49. while ((state != CONNECTED) &&
  50. (millis() - started < timeout))
  51. {
  52. run();
  53. }
  54. return state == CONNECTED;
  55. }
  56. void disconnect() {
  57. conn.disconnect();
  58. state = DISCONNECTED;
  59. BLYNK_LOG("Disconnected");
  60. }
  61. bool run(bool avail = false);
  62. #ifdef BLYNK_USE_DIRECT_CONNECT
  63. void setProfile(const char* json) {
  64. profile = json;
  65. }
  66. void startSession() {
  67. state = CONNECTING;
  68. #ifdef BLYNK_MSG_LIMIT
  69. deltaCmd = 1000;
  70. #endif
  71. currentMsgId = 0;
  72. lastHeartbeat = lastActivityIn = lastActivityOut = millis();
  73. }
  74. #endif
  75. 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);
  76. private:
  77. int readHeader(BlynkHeader& hdr);
  78. uint16_t getNextMsgId();
  79. protected:
  80. void begin(const char* auth) {
  81. //BLYNK_LOG("Blynk v" BLYNK_VERSION);
  82. this->authkey = auth;
  83. }
  84. bool processInput(void);
  85. Transp& conn;
  86. private:
  87. const char* authkey;
  88. #ifdef BLYNK_USE_DIRECT_CONNECT
  89. const char* profile;
  90. #endif
  91. uint32_t lastActivityIn;
  92. uint32_t lastActivityOut;
  93. uint32_t lastHeartbeat;
  94. #ifdef BLYNK_MSG_LIMIT
  95. uint32_t deltaCmd;
  96. #endif
  97. uint16_t currentMsgId;
  98. BlynkState state;
  99. };
  100. template <class Transp>
  101. bool BlynkProtocol<Transp>::run(bool avail)
  102. {
  103. #if !defined(BLYNK_NO_YIELD)
  104. yield();
  105. #endif
  106. if (state == DISCONNECTED) {
  107. #ifdef BLYNK_DEBUG
  108. BLYNK_LOG("run returning, disconnected");
  109. #endif
  110. return false;
  111. }
  112. const bool tconn = conn.connected();
  113. #ifdef BLYNK_DEBUG
  114. static unsigned long lastDebugLog = 0;
  115. unsigned long now = millis();
  116. if (now - lastDebugLog > 5000UL) {
  117. BLYNK_LOG("state %d, tconn %d", state, tconn);
  118. lastDebugLog = now;
  119. }
  120. #endif
  121. if (tconn) {
  122. if (avail || conn.available() > 0) {
  123. //BLYNK_LOG("Available: %d", conn.available());
  124. //const unsigned long t = micros();
  125. if (!processInput()) {
  126. conn.disconnect();
  127. // TODO: Only when in direct mode?
  128. #ifdef BLYNK_USE_DIRECT_CONNECT
  129. state = CONNECTING;
  130. #endif
  131. //BlynkOnDisconnected();
  132. return false;
  133. }
  134. //BLYNK_LOG("Proc time: %d", micros() - t);
  135. }
  136. }
  137. const unsigned long t = millis();
  138. if (state == CONNECTED) {
  139. if (!tconn) {
  140. state = CONNECTING;
  141. lastHeartbeat = t;
  142. //BlynkOnDisconnected();
  143. return false;
  144. }
  145. if (t - lastActivityIn > (1000UL * BLYNK_HEARTBEAT + BLYNK_TIMEOUT_MS*3)) {
  146. #ifdef BLYNK_DEBUG
  147. BLYNK_LOG("Heartbeat timeout (last in: %lu)", lastActivityIn);
  148. #else
  149. BLYNK_LOG("Heartbeat timeout");
  150. #endif
  151. conn.disconnect();
  152. state = CONNECTING;
  153. //BlynkOnDisconnected();
  154. return false;
  155. } else if ((t - lastActivityIn > 1000UL * BLYNK_HEARTBEAT ||
  156. t - lastActivityOut > 1000UL * BLYNK_HEARTBEAT) &&
  157. t - lastHeartbeat > BLYNK_TIMEOUT_MS)
  158. {
  159. // Send ping if we didn't either send or receive something
  160. // for BLYNK_HEARTBEAT seconds
  161. sendCmd(BLYNK_CMD_PING);
  162. lastHeartbeat = t;
  163. }
  164. } else if (state == CONNECTING) {
  165. #ifndef BLYNK_USE_DIRECT_CONNECT
  166. if (tconn && (t - lastHeartbeat > 20000UL)) {
  167. BLYNK_LOG("Login timeout");
  168. conn.disconnect();
  169. state = CONNECTING;
  170. return false;
  171. } else if (!tconn && (t - lastHeartbeat > 5000UL)) {
  172. conn.disconnect();
  173. if (!conn.connect()) {
  174. #ifdef BLYNK_DEBUG
  175. BLYNK_LOG("Connect failure");
  176. #endif
  177. lastHeartbeat = t;
  178. return false;
  179. }
  180. #ifdef BLYNK_DEBUG
  181. BLYNK_LOG("Connect success");
  182. #endif
  183. #ifdef BLYNK_MSG_LIMIT
  184. deltaCmd = 1000;
  185. #endif
  186. sendCmd(BLYNK_CMD_LOGIN, 1, authkey, strlen(authkey));
  187. lastHeartbeat = lastActivityOut;
  188. return true;
  189. }
  190. #endif
  191. }
  192. return true;
  193. }
  194. template <class Transp>
  195. BLYNK_FORCE_INLINE
  196. bool BlynkProtocol<Transp>::processInput(void)
  197. {
  198. BlynkHeader hdr;
  199. const int ret = readHeader(hdr);
  200. if (ret == 0) {
  201. return true; // Considered OK (no data on input)
  202. }
  203. if (ret < 0 || hdr.msg_id == 0) {
  204. #ifdef BLYNK_DEBUG
  205. BLYNK_LOG("Wrong header on input");
  206. #endif
  207. return false;
  208. }
  209. if (hdr.type == BLYNK_CMD_RESPONSE) {
  210. lastActivityIn = millis();
  211. #ifndef BLYNK_USE_DIRECT_CONNECT
  212. if (state == CONNECTING && (1 == hdr.msg_id)) {
  213. switch (hdr.length) {
  214. case BLYNK_SUCCESS:
  215. case BLYNK_ALREADY_LOGGED_IN:
  216. BLYNK_LOG("Ready (ping: %dms).", lastActivityIn-lastHeartbeat);
  217. lastHeartbeat = lastActivityIn;
  218. state = CONNECTED;
  219. this->sendInfo();
  220. #if !defined(BLYNK_NO_YIELD)
  221. yield();
  222. #endif
  223. BlynkOnConnected();
  224. return true;
  225. case BLYNK_INVALID_TOKEN:
  226. BLYNK_LOG("Invalid auth token");
  227. break;
  228. default:
  229. BLYNK_LOG("Connect failed (code: %d)", hdr.length);
  230. }
  231. return false;
  232. }
  233. if (BLYNK_NOT_AUTHENTICATED == hdr.length) {
  234. return false;
  235. }
  236. #endif
  237. // TODO: return code may indicate App presence
  238. return true;
  239. }
  240. if (hdr.length > BLYNK_MAX_READBYTES) {
  241. #ifdef DEBUG
  242. BLYNK_LOG("Packet size (%u) > max allowed (%u)", hdr.length, BLYNK_MAX_READBYTES);
  243. #endif
  244. return false;
  245. }
  246. uint8_t inputBuffer[hdr.length+1]; // Add 1 to zero-terminate
  247. if (hdr.length != conn.read(inputBuffer, hdr.length)) {
  248. #ifdef DEBUG
  249. BLYNK_LOG("Can't read body");
  250. #endif
  251. return false;
  252. }
  253. inputBuffer[hdr.length] = '\0';
  254. #ifdef BLYNK_DEBUG
  255. BLYNK_DBG_DUMP(">", inputBuffer, hdr.length);
  256. #endif
  257. lastActivityIn = millis();
  258. switch (hdr.type)
  259. {
  260. #ifdef BLYNK_USE_DIRECT_CONNECT
  261. case BLYNK_CMD_REGISTER:
  262. case BLYNK_CMD_SAVE_PROF:
  263. case BLYNK_CMD_ACTIVATE:
  264. case BLYNK_CMD_DEACTIVATE:
  265. case BLYNK_CMD_REFRESH:
  266. break; // those make no sense in direct mode
  267. case BLYNK_CMD_LOAD_PROF: {
  268. sendCmd(BLYNK_CMD_LOAD_PROF, hdr.msg_id, profile, strlen(profile));
  269. } break;
  270. case BLYNK_CMD_GET_TOKEN: {
  271. sendCmd(BLYNK_CMD_GET_TOKEN, hdr.msg_id, authkey, strlen(authkey));
  272. } break;
  273. case BLYNK_CMD_LOGIN:
  274. state = CONNECTED;
  275. // Fall-through
  276. #endif
  277. case BLYNK_CMD_PING: {
  278. sendCmd(BLYNK_CMD_RESPONSE, hdr.msg_id, NULL, BLYNK_SUCCESS);
  279. } break;
  280. case BLYNK_CMD_HARDWARE:
  281. case BLYNK_CMD_BRIDGE: {
  282. currentMsgId = hdr.msg_id;
  283. this->processCmd(inputBuffer, hdr.length);
  284. currentMsgId = 0;
  285. } break;
  286. default:
  287. #ifdef BLYNK_DEBUG
  288. BLYNK_LOG("Invalid header type: %d", hdr.type);
  289. #endif
  290. return false;
  291. }
  292. return true;
  293. }
  294. template <class Transp>
  295. int BlynkProtocol<Transp>::readHeader(BlynkHeader& hdr)
  296. {
  297. size_t rlen = conn.read(&hdr, sizeof(hdr));
  298. if (rlen == 0) {
  299. return 0;
  300. }
  301. if (sizeof(hdr) != rlen) {
  302. return -1;
  303. }
  304. hdr.msg_id = ntohs(hdr.msg_id);
  305. hdr.length = ntohs(hdr.length);
  306. #ifdef BLYNK_DEBUG
  307. BLYNK_LOG(">msg %d,%u,%u", hdr.type, hdr.msg_id, hdr.length);
  308. #endif
  309. return rlen;
  310. }
  311. template <class Transp>
  312. void BlynkProtocol<Transp>::sendCmd(uint8_t cmd, uint16_t id, const void* data, size_t length, const void* data2, size_t length2)
  313. {
  314. if (0 == id) {
  315. id = getNextMsgId();
  316. }
  317. #ifdef BLYNK_DEBUG
  318. BLYNK_LOG("<msg %d,%u,%u", cmd, id, length+length2);
  319. #endif
  320. if (!conn.connected() || (cmd != BLYNK_CMD_LOGIN && state != CONNECTED) ) {
  321. #ifdef BLYNK_DEBUG
  322. BLYNK_LOG("Cmd skipped");
  323. #endif
  324. return;
  325. }
  326. #if defined(BLYNK_SEND_ATOMIC)|| defined(ESP8266) || defined(SPARK) || defined(PARTICLE) || defined(ENERGIA) || defined(SEND_IN_CHUNKS)
  327. // Those have more RAM and like single write at a time...
  328. uint8_t buff[BLYNK_MAX_READBYTES]; // TODO: Eliminate constant
  329. BlynkHeader* hdr = (BlynkHeader*)buff;
  330. hdr->type = cmd;
  331. hdr->msg_id = htons(id);
  332. hdr->length = htons(length+length2);
  333. size_t len2s = sizeof(BlynkHeader);
  334. if (data && length) {
  335. memcpy(buff + len2s, data, length);
  336. len2s += length;
  337. }
  338. if (data2 && length2) {
  339. memcpy(buff + len2s, data2, length2);
  340. len2s += length2;
  341. }
  342. #ifdef BLYNK_DEBUG
  343. BLYNK_DBG_DUMP("<", buff+5, len2s-5);
  344. #endif
  345. size_t wlen = 0;
  346. #ifndef BLYNK_SEND_CHUNK
  347. #define BLYNK_SEND_CHUNK 1024 // Just a big number
  348. #endif
  349. while (wlen < len2s) {
  350. const size_t chunk = BlynkMin(size_t(BLYNK_SEND_CHUNK), len2s - wlen);
  351. const size_t wlentmp = conn.write(buff + wlen, chunk);
  352. if (wlentmp == 0) {
  353. #ifdef BLYNK_DEBUG
  354. BLYNK_LOG("Cmd error");
  355. #endif
  356. conn.disconnect();
  357. state = CONNECTING;
  358. //BlynkOnDisconnected();
  359. return;
  360. }
  361. wlen += wlentmp;
  362. #ifdef BLYNK_SEND_THROTTLE
  363. delay(BLYNK_SEND_THROTTLE);
  364. #endif
  365. }
  366. if (wlen != len2s) {
  367. #ifdef BLYNK_DEBUG
  368. BLYNK_LOG("Sent %u/%u", wlen, len2s);
  369. #endif
  370. conn.disconnect();
  371. state = CONNECTING;
  372. //BlynkOnDisconnected();
  373. return;
  374. }
  375. #else
  376. BlynkHeader hdr;
  377. hdr.type = cmd;
  378. hdr.msg_id = htons(id);
  379. hdr.length = htons(length+length2);
  380. size_t wlen = conn.write(&hdr, sizeof(hdr));
  381. if (cmd != BLYNK_CMD_RESPONSE) {
  382. if (length) {
  383. #ifdef BLYNK_DEBUG
  384. BLYNK_DBG_DUMP("<", data, length);
  385. #endif
  386. wlen += conn.write(data, length);
  387. }
  388. if (length2) {
  389. #ifdef BLYNK_DEBUG
  390. BLYNK_DBG_DUMP("<", data2, length2);
  391. #endif
  392. wlen += conn.write(data2, length2);
  393. }
  394. if (wlen != sizeof(hdr)+length+length2) {
  395. #ifdef BLYNK_DEBUG
  396. BLYNK_LOG("Sent %u/%u", wlen, sizeof(hdr)+length+length2);
  397. #endif
  398. conn.disconnect();
  399. state = CONNECTING;
  400. //BlynkOnDisconnected();
  401. return;
  402. }
  403. }
  404. #endif
  405. #if defined BLYNK_MSG_LIMIT && BLYNK_MSG_LIMIT > 0
  406. const uint32_t ts = millis();
  407. BlynkAverageSample<32>(deltaCmd, ts - lastActivityOut);
  408. lastActivityOut = ts;
  409. //BLYNK_LOG("Delta: %u", deltaCmd);
  410. if (deltaCmd < (1000/BLYNK_MSG_LIMIT)) {
  411. BLYNK_LOG_TROUBLE("flood-error");
  412. conn.disconnect();
  413. state = CONNECTING;
  414. //BlynkOnDisconnected();
  415. }
  416. #else
  417. lastActivityOut = millis();
  418. #endif
  419. }
  420. template <class Transp>
  421. uint16_t BlynkProtocol<Transp>::getNextMsgId()
  422. {
  423. static uint16_t last = 0;
  424. if (currentMsgId != 0)
  425. return currentMsgId;
  426. if (++last == 0)
  427. last = 1;
  428. return last;
  429. }
  430. #endif