Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

695 lines
20 KiB

6 years ago
  1. /*
  2. ITEAD RF BRIDGE MODULE
  3. Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #ifdef ITEAD_SONOFF_RFBRIDGE
  6. #include <queue>
  7. #include <Ticker.h>
  8. #if RFB_DIRECT
  9. #include <RCSwitch.h>
  10. #endif
  11. // -----------------------------------------------------------------------------
  12. // DEFINITIONS
  13. // -----------------------------------------------------------------------------
  14. // EFM8 Protocol
  15. #define RF_MESSAGE_SIZE 9
  16. #define RF_MAX_MESSAGE_SIZE (112+4)
  17. #define RF_CODE_START 0xAA
  18. #define RF_CODE_ACK 0xA0
  19. #define RF_CODE_LEARN 0xA1
  20. #define RF_CODE_LEARN_KO 0xA2
  21. #define RF_CODE_LEARN_OK 0xA3
  22. #define RF_CODE_RFIN 0xA4
  23. #define RF_CODE_RFOUT 0xA5
  24. #define RF_CODE_SNIFFING_ON 0xA6
  25. #define RF_CODE_SNIFFING_OFF 0xA7
  26. #define RF_CODE_RFOUT_NEW 0xA8
  27. #define RF_CODE_LEARN_NEW 0xA9
  28. #define RF_CODE_LEARN_KO_NEW 0xAA
  29. #define RF_CODE_LEARN_OK_NEW 0xAB
  30. #define RF_CODE_RFOUT_BUCKET 0xB0
  31. #define RF_CODE_STOP 0x55
  32. // Settings
  33. #define RF_MAX_KEY_LENGTH (9)
  34. // -----------------------------------------------------------------------------
  35. // GLOBALS TO THE MODULE
  36. // -----------------------------------------------------------------------------
  37. unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
  38. unsigned char _uartpos = 0;
  39. unsigned char _learnId = 0;
  40. bool _learnStatus = true;
  41. bool _rfbin = false;
  42. typedef struct {
  43. byte code[RF_MESSAGE_SIZE];
  44. byte times;
  45. } rfb_message_t;
  46. static std::queue<rfb_message_t> _rfb_message_queue;
  47. Ticker _rfb_ticker;
  48. bool _rfb_ticker_active = false;
  49. #if RFB_DIRECT
  50. RCSwitch * _rfModem;
  51. bool _learning = false;
  52. #endif
  53. // -----------------------------------------------------------------------------
  54. // PRIVATES
  55. // -----------------------------------------------------------------------------
  56. /*
  57. From an hexa char array ("A220EE...") to a byte array (half the size)
  58. */
  59. static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) {
  60. int n = strlen(in);
  61. if (n > RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0;
  62. char tmp[3] = {0,0,0};
  63. n /= 2;
  64. for (unsigned char p = 0; p<n; p++) {
  65. memcpy(tmp, &in[p*2], 2);
  66. out[p] = strtol(tmp, NULL, 16);
  67. }
  68. return n;
  69. }
  70. /*
  71. From a byte array to an hexa char array ("A220EE...", double the size)
  72. */
  73. static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
  74. for (unsigned char p = 0; p<n; p++) {
  75. sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
  76. }
  77. return true;
  78. }
  79. void _rfbWebSocketOnSend(JsonObject& root) {
  80. root["rfbVisible"] = 1;
  81. root["rfbCount"] = relayCount();
  82. #if RF_RAW_SUPPORT
  83. root["rfbrawVisible"] = 1;
  84. #endif
  85. JsonArray& rfb = root.createNestedArray("rfb");
  86. for (byte id=0; id<relayCount(); id++) {
  87. for (byte status=0; status<2; status++) {
  88. JsonObject& node = rfb.createNestedObject();
  89. node["id"] = id;
  90. node["status"] = status;
  91. node["data"] = rfbRetrieve(id, status == 1);
  92. }
  93. }
  94. }
  95. void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  96. if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
  97. if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
  98. if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
  99. }
  100. void _rfbAck() {
  101. #if not RFB_DIRECT
  102. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
  103. Serial.println();
  104. Serial.write(RF_CODE_START);
  105. Serial.write(RF_CODE_ACK);
  106. Serial.write(RF_CODE_STOP);
  107. Serial.flush();
  108. Serial.println();
  109. #endif
  110. }
  111. void _rfbLearn() {
  112. #if RFB_DIRECT
  113. DEBUG_MSG_P(PSTR("[RFBRIDGE] Entering LEARN mode\n"));
  114. _learning = true;
  115. #else
  116. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
  117. Serial.println();
  118. Serial.write(RF_CODE_START);
  119. Serial.write(RF_CODE_LEARN);
  120. Serial.write(RF_CODE_STOP);
  121. Serial.flush();
  122. Serial.println();
  123. #endif
  124. #if WEB_SUPPORT
  125. char buffer[100];
  126. snprintf_P(buffer, sizeof(buffer), PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0);
  127. wsSend(buffer);
  128. #endif
  129. }
  130. void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
  131. for (unsigned char j=0; j<n; j++) {
  132. Serial.write(message[j]);
  133. }
  134. }
  135. void _rfbSend(byte * message) {
  136. #if RFB_DIRECT
  137. unsigned int protocol = message[1];
  138. unsigned int timing =
  139. (message[2] << 8) |
  140. (message[3] << 0) ;
  141. unsigned int bitlength = message[4];
  142. unsigned long rf_code =
  143. (message[5] << 24) |
  144. (message[6] << 16) |
  145. (message[7] << 8) |
  146. (message[8] << 0) ;
  147. _rfModem->setProtocol(protocol);
  148. if (timing > 0) {
  149. _rfModem->setPulseLength(timing);
  150. }
  151. _rfModem->send(rf_code, bitlength);
  152. _rfModem->resetAvailable();
  153. #else
  154. Serial.println();
  155. Serial.write(RF_CODE_START);
  156. Serial.write(RF_CODE_RFOUT);
  157. _rfbSendRaw(message);
  158. Serial.write(RF_CODE_STOP);
  159. Serial.flush();
  160. Serial.println();
  161. #endif
  162. }
  163. void _rfbSend() {
  164. // Check if there is something in the queue
  165. if (_rfb_message_queue.empty()) return;
  166. // Pop the first element
  167. rfb_message_t message = _rfb_message_queue.front();
  168. _rfb_message_queue.pop();
  169. // Send the message
  170. _rfbSend(message.code);
  171. // If it should be further sent, push it to the stack again
  172. if (message.times > 1) {
  173. message.times = message.times - 1;
  174. _rfb_message_queue.push(message);
  175. }
  176. // if there are still messages in the queue...
  177. if (_rfb_message_queue.empty()) {
  178. _rfb_ticker.detach();
  179. _rfb_ticker_active = false;
  180. }
  181. }
  182. void _rfbSend(byte * code, unsigned char times) {
  183. #if RFB_DIRECT
  184. times = 1;
  185. #endif
  186. char buffer[RF_MESSAGE_SIZE];
  187. _rfbToChar(code, buffer);
  188. DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
  189. rfb_message_t message;
  190. memcpy(message.code, code, RF_MESSAGE_SIZE);
  191. message.times = times;
  192. _rfb_message_queue.push(message);
  193. // Enable the ticker if not running
  194. if (!_rfb_ticker_active) {
  195. _rfb_ticker_active = true;
  196. _rfb_ticker.attach_ms(RF_SEND_DELAY, _rfbSend);
  197. }
  198. }
  199. #if RF_RAW_SUPPORT
  200. void _rfbSendRawOnce(byte *code, unsigned char length) {
  201. char buffer[length*2];
  202. _rfbToChar(code, buffer, length);
  203. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending RAW MESSAGE '%s'\n"), buffer);
  204. _rfbSendRaw(code, length);
  205. }
  206. #endif // RF_RAW_SUPPORT
  207. bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) {
  208. if (strlen(code) != 18) return false;
  209. bool found = false;
  210. String compareto = String(&code[12]);
  211. compareto.toUpperCase();
  212. DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str());
  213. for (unsigned char i=0; i<relayCount(); i++) {
  214. String code_on = rfbRetrieve(i, true);
  215. if (code_on.length() && code_on.endsWith(compareto)) {
  216. DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
  217. value = 1;
  218. found = true;
  219. if (buffer) strcpy(buffer, code_on.c_str());
  220. }
  221. String code_off = rfbRetrieve(i, false);
  222. if (code_off.length() && code_off.endsWith(compareto)) {
  223. DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
  224. if (found) value = 2;
  225. found = true;
  226. if (buffer) strcpy(buffer, code_off.c_str());
  227. }
  228. if (found) {
  229. relayID = i;
  230. return true;
  231. }
  232. }
  233. return false;
  234. }
  235. void _rfbDecode() {
  236. static unsigned long last = 0;
  237. if (millis() - last < RF_RECEIVE_DELAY) return;
  238. last = millis();
  239. byte action = _uartbuf[0];
  240. char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
  241. DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
  242. if (action == RF_CODE_LEARN_KO) {
  243. _rfbAck();
  244. DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
  245. #if WEB_SUPPORT
  246. wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
  247. #endif
  248. }
  249. if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
  250. _rfbAck();
  251. _rfbToChar(&_uartbuf[1], buffer);
  252. DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
  253. }
  254. if (action == RF_CODE_LEARN_OK) {
  255. DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
  256. rfbStore(_learnId, _learnStatus, buffer);
  257. // Websocket update
  258. #if WEB_SUPPORT
  259. char wsb[100];
  260. snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
  261. wsSend(wsb);
  262. #endif
  263. }
  264. if (action == RF_CODE_RFIN) {
  265. /* Look for the code, possibly replacing the code with the exact learned one on match
  266. * we want to do this on learn too to be sure that the learned code is the same if it
  267. * is equivalent
  268. */
  269. unsigned char id;
  270. unsigned char status;
  271. bool matched = _rfbMatch(buffer, id, status, buffer);
  272. if (matched) {
  273. DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
  274. _rfbin = true;
  275. if (status == 2) {
  276. relayToggle(id);
  277. } else {
  278. relayStatus(id, status == 1);
  279. }
  280. }
  281. #if MQTT_SUPPORT
  282. mqttSend(MQTT_TOPIC_RFIN, buffer);
  283. #endif
  284. }
  285. }
  286. void _rfbReceive() {
  287. #if RFB_DIRECT
  288. static long learn_start = 0;
  289. if (!_learning && learn_start) {
  290. learn_start = 0;
  291. }
  292. if (_learning) {
  293. if (!learn_start) {
  294. DEBUG_MSG_P(PSTR("[RFBRIDGE] arming learn timeout\n"));
  295. learn_start = millis();
  296. }
  297. if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) {
  298. DEBUG_MSG_P(PSTR("[RFBRIDGE] learn timeout triggered\n"));
  299. memset(_uartbuf, 0, sizeof(_uartbuf));
  300. _uartbuf[0] = RF_CODE_LEARN_KO;
  301. _rfbDecode();
  302. _learning = false;
  303. }
  304. }
  305. if (_rfModem->available()) {
  306. static unsigned long last = 0;
  307. if (millis() - last > RF_DEBOUNCE) {
  308. last = millis();
  309. unsigned long rf_code = _rfModem->getReceivedValue();
  310. if ( rf_code > 0) {
  311. DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
  312. unsigned int timing = _rfModem->getReceivedDelay();
  313. memset(_uartbuf, 0, sizeof(_uartbuf));
  314. unsigned char *msgbuf = _uartbuf + 1;
  315. _uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN;
  316. msgbuf[0] = 0xC0;
  317. msgbuf[1] = _rfModem->getReceivedProtocol();
  318. msgbuf[2] = timing >> 8;
  319. msgbuf[3] = timing >> 0;
  320. msgbuf[4] = _rfModem->getReceivedBitlength();
  321. msgbuf[5] = rf_code >> 24;
  322. msgbuf[6] = rf_code >> 16;
  323. msgbuf[7] = rf_code >> 8;
  324. msgbuf[8] = rf_code >> 0;
  325. _rfbDecode();
  326. _learning = false;
  327. }
  328. }
  329. _rfModem->resetAvailable();
  330. }
  331. #else
  332. static bool receiving = false;
  333. while (Serial.available()) {
  334. yield();
  335. byte c = Serial.read();
  336. //DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
  337. if (receiving) {
  338. if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
  339. _rfbDecode();
  340. receiving = false;
  341. } else if (_uartpos <= RF_MESSAGE_SIZE) {
  342. _uartbuf[_uartpos++] = c;
  343. } else {
  344. // wrong message, should have received a RF_CODE_STOP
  345. receiving = false;
  346. }
  347. } else if (c == RF_CODE_START) {
  348. _uartpos = 0;
  349. receiving = true;
  350. }
  351. }
  352. #endif
  353. }
  354. bool _rfbCompare(const char * code1, const char * code2) {
  355. return strcmp(&code1[12], &code2[12]) == 0;
  356. }
  357. bool _rfbSameOnOff(unsigned char id) {
  358. return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
  359. }
  360. #if MQTT_SUPPORT
  361. void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
  362. if (type == MQTT_CONNECT_EVENT) {
  363. char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
  364. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
  365. mqttSubscribe(buffer);
  366. mqttSubscribe(MQTT_TOPIC_RFOUT);
  367. #if RF_RAW_SUPPORT
  368. mqttSubscribe(MQTT_TOPIC_RFRAW);
  369. #endif
  370. }
  371. if (type == MQTT_MESSAGE_EVENT) {
  372. // Match topic
  373. String t = mqttMagnitude((char *) topic);
  374. // Check if should go into learn mode
  375. if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
  376. _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
  377. if (_learnId >= relayCount()) {
  378. DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
  379. return;
  380. }
  381. _learnStatus = (char)payload[0] != '0';
  382. _rfbLearn();
  383. return;
  384. }
  385. bool isRFOut = t.equals(MQTT_TOPIC_RFOUT);
  386. #if RF_RAW_SUPPORT
  387. bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW);
  388. #else
  389. bool isRFRaw = false;
  390. #endif
  391. if (isRFOut || isRFRaw) {
  392. // The payload may be a code in HEX format ([0-9A-Z]{18}) or
  393. // the code comma the number of times to transmit it.
  394. char * tok = strtok((char *) payload, ",");
  395. // Check if a switch is linked to that message
  396. unsigned char id;
  397. unsigned char status = 0;
  398. if (_rfbMatch(tok, id, status)) {
  399. if (status == 2) {
  400. relayToggle(id);
  401. } else {
  402. relayStatus(id, status == 1);
  403. }
  404. return;
  405. }
  406. #if RF_RAW_SUPPORT
  407. byte message[RF_MAX_MESSAGE_SIZE];
  408. int len = _rfbToArray(tok, message, 0);
  409. if ((len > 0) && (isRFRaw || len != RF_MESSAGE_SIZE)) {
  410. _rfbSendRawOnce(message, len);
  411. } else {
  412. tok = strtok(NULL, ",");
  413. byte times = (tok != NULL) ? atoi(tok) : 1;
  414. _rfbSend(message, times);
  415. }
  416. #else // RF_RAW_SUPPORT
  417. byte message[RF_MESSAGE_SIZE];
  418. if (_rfbToArray(tok, message)) {
  419. tok = strtok(NULL, ",");
  420. byte times = (tok != NULL) ? atoi(tok) : 1;
  421. _rfbSend(message, times);
  422. }
  423. #endif // RF_RAW_SUPPORT
  424. }
  425. }
  426. }
  427. #endif
  428. #if TERMINAL_SUPPORT
  429. void _rfbInitCommands() {
  430. settingsRegisterCommand(F("LEARN"), [](Embedis* e) {
  431. if (e->argc < 3) {
  432. DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
  433. return;
  434. }
  435. int id = String(e->argv[1]).toInt();
  436. if (id >= relayCount()) {
  437. DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
  438. return;
  439. }
  440. int status = String(e->argv[2]).toInt();
  441. rfbLearn(id, status == 1);
  442. DEBUG_MSG_P(PSTR("+OK\n"));
  443. });
  444. settingsRegisterCommand(F("FORGET"), [](Embedis* e) {
  445. if (e->argc < 3) {
  446. DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
  447. return;
  448. }
  449. int id = String(e->argv[1]).toInt();
  450. if (id >= relayCount()) {
  451. DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
  452. return;
  453. }
  454. int status = String(e->argv[2]).toInt();
  455. rfbForget(id, status == 1);
  456. DEBUG_MSG_P(PSTR("+OK\n"));
  457. });
  458. }
  459. #endif // TERMINAL_SUPPORT
  460. // -----------------------------------------------------------------------------
  461. // PUBLIC
  462. // -----------------------------------------------------------------------------
  463. void rfbStore(unsigned char id, bool status, const char * code) {
  464. DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
  465. char key[RF_MAX_KEY_LENGTH] = {0};
  466. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  467. setSetting(key, code);
  468. }
  469. String rfbRetrieve(unsigned char id, bool status) {
  470. char key[RF_MAX_KEY_LENGTH] = {0};
  471. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  472. return getSetting(key);
  473. }
  474. void rfbStatus(unsigned char id, bool status) {
  475. String value = rfbRetrieve(id, status);
  476. if (value.length() > 0) {
  477. bool same = _rfbSameOnOff(id);
  478. #if RF_RAW_SUPPORT
  479. byte message[RF_MAX_MESSAGE_SIZE];
  480. int len = _rfbToArray(value.c_str(), message, 0);
  481. if (len == RF_MESSAGE_SIZE && // probably a standard msg
  482. (message[0] != RF_CODE_START || // raw would start with 0xAA
  483. message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0,
  484. message[2] + 4 != len || // needs a valid length,
  485. message[len-1] != RF_CODE_STOP)) { // and finish with 0x55
  486. if (!_rfbin) {
  487. unsigned char times = same ? 1 : RF_SEND_TIMES;
  488. _rfbSend(message, times);
  489. }
  490. } else {
  491. _rfbSendRawOnce(message, len); // send a raw message
  492. }
  493. #else // RF_RAW_SUPPORT
  494. if (!_rfbin) {
  495. byte message[RF_MESSAGE_SIZE];
  496. _rfbToArray(value.c_str(), message);
  497. unsigned char times = same ? 1 : RF_SEND_TIMES;
  498. _rfbSend(message, times);
  499. }
  500. #endif // RF_RAW_SUPPORT
  501. }
  502. _rfbin = false;
  503. }
  504. void rfbLearn(unsigned char id, bool status) {
  505. _learnId = id;
  506. _learnStatus = status;
  507. _rfbLearn();
  508. }
  509. void rfbForget(unsigned char id, bool status) {
  510. char key[RF_MAX_KEY_LENGTH] = {0};
  511. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  512. delSetting(key);
  513. // Websocket update
  514. #if WEB_SUPPORT
  515. char wsb[100];
  516. snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
  517. wsSend(wsb);
  518. #endif
  519. }
  520. // -----------------------------------------------------------------------------
  521. // SETUP & LOOP
  522. // -----------------------------------------------------------------------------
  523. void rfbSetup() {
  524. #if MQTT_SUPPORT
  525. mqttRegister(_rfbMqttCallback);
  526. #endif
  527. #if WEB_SUPPORT
  528. wsOnSendRegister(_rfbWebSocketOnSend);
  529. wsOnActionRegister(_rfbWebSocketOnAction);
  530. #endif
  531. #if TERMINAL_SUPPORT
  532. _rfbInitCommands();
  533. #endif
  534. #if RFB_DIRECT
  535. _rfModem = new RCSwitch();
  536. _rfModem->enableReceive(RFB_RX_PIN);
  537. _rfModem->enableTransmit(RFB_TX_PIN);
  538. _rfModem->setRepeatTransmit(6);
  539. DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN);
  540. DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
  541. #endif
  542. // Register loop
  543. espurnaRegisterLoop(rfbLoop);
  544. }
  545. void rfbLoop() {
  546. _rfbReceive();
  547. }
  548. #endif