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.

778 lines
20 KiB

Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
  1. /*
  2. RF BRIDGE MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "rfbridge.h"
  6. #if RF_SUPPORT
  7. #include <queue>
  8. #include "api.h"
  9. #include "relay.h"
  10. #include "terminal.h"
  11. #include "mqtt.h"
  12. #include "ws.h"
  13. #include "utils.h"
  14. // -----------------------------------------------------------------------------
  15. // DEFINITIONS
  16. // -----------------------------------------------------------------------------
  17. // EFM8 Protocol
  18. #define RF_MESSAGE_SIZE 9
  19. #define RF_MAX_MESSAGE_SIZE (112+4)
  20. #define RF_CODE_START 0xAA
  21. #define RF_CODE_ACK 0xA0
  22. #define RF_CODE_LEARN 0xA1
  23. #define RF_CODE_LEARN_KO 0xA2
  24. #define RF_CODE_LEARN_OK 0xA3
  25. #define RF_CODE_RFIN 0xA4
  26. #define RF_CODE_RFOUT 0xA5
  27. #define RF_CODE_SNIFFING_ON 0xA6
  28. #define RF_CODE_SNIFFING_OFF 0xA7
  29. #define RF_CODE_RFOUT_NEW 0xA8
  30. #define RF_CODE_LEARN_NEW 0xA9
  31. #define RF_CODE_LEARN_KO_NEW 0xAA
  32. #define RF_CODE_LEARN_OK_NEW 0xAB
  33. #define RF_CODE_RFOUT_BUCKET 0xB0
  34. #define RF_CODE_STOP 0x55
  35. // Settings
  36. #define RF_MAX_KEY_LENGTH (9)
  37. // -----------------------------------------------------------------------------
  38. // GLOBALS TO THE MODULE
  39. // -----------------------------------------------------------------------------
  40. unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
  41. unsigned char _uartpos = 0;
  42. unsigned char _learnId = 0;
  43. bool _learnStatus = true;
  44. bool _rfbin = false;
  45. struct rfb_message_t {
  46. uint8_t code[RF_MESSAGE_SIZE];
  47. uint8_t times;
  48. };
  49. static std::queue<rfb_message_t> _rfb_message_queue;
  50. #if RFB_DIRECT
  51. RCSwitch * _rfModem;
  52. bool _learning = false;
  53. #endif
  54. bool _rfb_receive = false;
  55. bool _rfb_transmit = false;
  56. unsigned char _rfb_repeat = RF_SEND_TIMES;
  57. // -----------------------------------------------------------------------------
  58. // PRIVATES
  59. // -----------------------------------------------------------------------------
  60. #if WEB_SUPPORT
  61. void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned char size) {
  62. JsonObject& rfb = root.createNestedObject("rfb");
  63. rfb["size"] = size;
  64. rfb["start"] = start;
  65. JsonArray& on = rfb.createNestedArray("on");
  66. JsonArray& off = rfb.createNestedArray("off");
  67. for (uint8_t id=start; id<start+size; id++) {
  68. on.add(rfbRetrieve(id, true));
  69. off.add(rfbRetrieve(id, false));
  70. }
  71. }
  72. void _rfbWebSocketOnVisible(JsonObject& root) {
  73. root["rfbVisible"] = 1;
  74. }
  75. void _rfbWebSocketOnConnected(JsonObject& root) {
  76. root["rfbRepeat"] = getSetting("rfbRepeat", RF_SEND_TIMES);
  77. root["rfbCount"] = relayCount();
  78. #if RFB_DIRECT
  79. root["rfbdirectVisible"] = 1;
  80. root["rfbRX"] = getSetting("rfbRX", RFB_RX_PIN);
  81. root["rfbTX"] = getSetting("rfbTX", RFB_TX_PIN);
  82. #endif
  83. }
  84. void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  85. if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
  86. if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
  87. if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
  88. }
  89. bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  90. return (strncmp(key, "rfb", 3) == 0);
  91. }
  92. void _rfbWebSocketOnData(JsonObject& root) {
  93. _rfbWebSocketSendCodeArray(root, 0, relayCount());
  94. }
  95. #endif // WEB_SUPPORT
  96. void _rfbAckImpl();
  97. void _rfbLearnImpl();
  98. void _rfbSendImpl(uint8_t * message);
  99. void _rfbReceiveImpl();
  100. bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) {
  101. if (strlen(code) != 18) return false;
  102. bool found = false;
  103. String compareto = String(&code[12]);
  104. compareto.toUpperCase();
  105. DEBUG_MSG_P(PSTR("[RF] Trying to match code %s\n"), compareto.c_str());
  106. for (unsigned char i=0; i<relayCount(); i++) {
  107. String code_on = rfbRetrieve(i, true);
  108. if (code_on.length() && code_on.endsWith(compareto)) {
  109. DEBUG_MSG_P(PSTR("[RF] Match ON code for relay %d\n"), i);
  110. value = 1;
  111. found = true;
  112. if (buffer) strcpy(buffer, code_on.c_str());
  113. }
  114. String code_off = rfbRetrieve(i, false);
  115. if (code_off.length() && code_off.endsWith(compareto)) {
  116. DEBUG_MSG_P(PSTR("[RF] Match OFF code for relay %d\n"), i);
  117. if (found) value = 2;
  118. found = true;
  119. if (buffer) strcpy(buffer, code_off.c_str());
  120. }
  121. if (found) {
  122. relayID = i;
  123. return true;
  124. }
  125. }
  126. return false;
  127. }
  128. void _rfbDecode() {
  129. static unsigned long last = 0;
  130. if (millis() - last < RF_RECEIVE_DELAY) return;
  131. last = millis();
  132. uint8_t action = _uartbuf[0];
  133. char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
  134. DEBUG_MSG_P(PSTR("[RF] Action 0x%02X\n"), action);
  135. if (action == RF_CODE_LEARN_KO) {
  136. _rfbAckImpl();
  137. DEBUG_MSG_P(PSTR("[RF] Learn timeout\n"));
  138. }
  139. if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
  140. _rfbAckImpl();
  141. if (hexEncode(&_uartbuf[1], RF_MESSAGE_SIZE, buffer, sizeof(buffer))) {
  142. DEBUG_MSG_P(PSTR("[RF] Received message '%s'\n"), buffer);
  143. }
  144. }
  145. if (action == RF_CODE_LEARN_OK) {
  146. DEBUG_MSG_P(PSTR("[RF] Learn success\n"));
  147. rfbStore(_learnId, _learnStatus, buffer);
  148. // Websocket update
  149. #if WEB_SUPPORT
  150. wsPost([](JsonObject& root) {
  151. _rfbWebSocketSendCodeArray(root, _learnId, 1);
  152. });
  153. #endif
  154. }
  155. if (action == RF_CODE_RFIN) {
  156. /* Look for the code, possibly replacing the code with the exact learned one on match
  157. * we want to do this on learn too to be sure that the learned code is the same if it
  158. * is equivalent
  159. */
  160. unsigned char id;
  161. unsigned char status;
  162. bool matched = _rfbMatch(buffer, id, status, buffer);
  163. if (matched) {
  164. DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer);
  165. _rfbin = true;
  166. if (status == 2) {
  167. relayToggle(id);
  168. } else {
  169. relayStatus(id, status == 1);
  170. }
  171. }
  172. #if MQTT_SUPPORT
  173. mqttSend(MQTT_TOPIC_RFIN, buffer, false, false);
  174. #endif
  175. }
  176. }
  177. //
  178. // RF handler implementations
  179. //
  180. #if !RFB_DIRECT // Default for ITEAD SONOFF RFBRIDGE
  181. void _rfbSendRaw(const uint8_t *message, unsigned char size) {
  182. Serial.write(message, size);
  183. }
  184. void _rfbAckImpl() {
  185. DEBUG_MSG_P(PSTR("[RF] Sending ACK\n"));
  186. Serial.println();
  187. Serial.write(RF_CODE_START);
  188. Serial.write(RF_CODE_ACK);
  189. Serial.write(RF_CODE_STOP);
  190. Serial.flush();
  191. Serial.println();
  192. }
  193. void _rfbLearnImpl() {
  194. DEBUG_MSG_P(PSTR("[RF] Sending LEARN\n"));
  195. Serial.println();
  196. Serial.write(RF_CODE_START);
  197. Serial.write(RF_CODE_LEARN);
  198. Serial.write(RF_CODE_STOP);
  199. Serial.flush();
  200. Serial.println();
  201. }
  202. void _rfbSendImpl(uint8_t * message) {
  203. Serial.println();
  204. Serial.write(RF_CODE_START);
  205. Serial.write(RF_CODE_RFOUT);
  206. _rfbSendRaw(message, RF_MESSAGE_SIZE);
  207. Serial.write(RF_CODE_STOP);
  208. Serial.flush();
  209. Serial.println();
  210. }
  211. void _rfbReceiveImpl() {
  212. static bool receiving = false;
  213. while (Serial.available()) {
  214. yield();
  215. uint8_t c = Serial.read();
  216. //DEBUG_MSG_P(PSTR("[RF] Received 0x%02X\n"), c);
  217. if (receiving) {
  218. if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == RF_MESSAGE_SIZE + 1)) {
  219. _rfbDecode();
  220. receiving = false;
  221. } else if (_uartpos <= RF_MESSAGE_SIZE) {
  222. _uartbuf[_uartpos++] = c;
  223. } else {
  224. // wrong message, should have received a RF_CODE_STOP
  225. receiving = false;
  226. }
  227. } else if (c == RF_CODE_START) {
  228. _uartpos = 0;
  229. receiving = true;
  230. }
  231. }
  232. }
  233. void _rfbParseRaw(char * raw) {
  234. int rawlen = strlen(raw);
  235. if (rawlen > (RF_MAX_MESSAGE_SIZE * 2)) return;
  236. if ((rawlen < 2) || (rawlen & 1)) return;
  237. DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE '%s'\n"), raw);
  238. uint8_t message[RF_MAX_MESSAGE_SIZE];
  239. size_t bytes = hexDecode(raw, (size_t)rawlen, message, sizeof(message));
  240. _rfbSendRaw(message, bytes);
  241. }
  242. #else // RFB_DIRECT
  243. void _rfbAckImpl() {}
  244. void _rfbLearnImpl() {
  245. DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n"));
  246. _learning = true;
  247. }
  248. void _rfbSendImpl(uint8_t * message) {
  249. if (!_rfb_transmit) return;
  250. unsigned int protocol = message[1];
  251. unsigned int timing =
  252. (message[2] << 8) |
  253. (message[3] << 0) ;
  254. unsigned int bitlength = message[4];
  255. unsigned long rf_code =
  256. (message[5] << 24) |
  257. (message[6] << 16) |
  258. (message[7] << 8) |
  259. (message[8] << 0) ;
  260. _rfModem->setProtocol(protocol);
  261. if (timing > 0) {
  262. _rfModem->setPulseLength(timing);
  263. }
  264. _rfModem->send(rf_code, bitlength);
  265. _rfModem->resetAvailable();
  266. }
  267. void _rfbReceiveImpl() {
  268. if (!_rfb_receive) return;
  269. static long learn_start = 0;
  270. if (!_learning && learn_start) {
  271. learn_start = 0;
  272. }
  273. if (_learning) {
  274. if (!learn_start) {
  275. DEBUG_MSG_P(PSTR("[RF] Arming learn timeout\n"));
  276. learn_start = millis();
  277. }
  278. if (learn_start > 0 && millis() - learn_start > RF_LEARN_TIMEOUT) {
  279. DEBUG_MSG_P(PSTR("[RF] Learn timeout triggered\n"));
  280. memset(_uartbuf, 0, sizeof(_uartbuf));
  281. _uartbuf[0] = RF_CODE_LEARN_KO;
  282. _rfbDecode();
  283. _learning = false;
  284. }
  285. }
  286. if (_rfModem->available()) {
  287. static unsigned long last = 0;
  288. if (millis() - last > RF_DEBOUNCE) {
  289. last = millis();
  290. unsigned long rf_code = _rfModem->getReceivedValue();
  291. if ( rf_code > 0) {
  292. DEBUG_MSG_P(PSTR("[RF] Received code: %08X\n"), rf_code);
  293. unsigned int timing = _rfModem->getReceivedDelay();
  294. memset(_uartbuf, 0, sizeof(_uartbuf));
  295. unsigned char *msgbuf = _uartbuf + 1;
  296. _uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN;
  297. msgbuf[0] = 0xC0;
  298. msgbuf[1] = _rfModem->getReceivedProtocol();
  299. msgbuf[2] = timing >> 8;
  300. msgbuf[3] = timing >> 0;
  301. msgbuf[4] = _rfModem->getReceivedBitlength();
  302. msgbuf[5] = rf_code >> 24;
  303. msgbuf[6] = rf_code >> 16;
  304. msgbuf[7] = rf_code >> 8;
  305. msgbuf[8] = rf_code >> 0;
  306. _rfbDecode();
  307. _learning = false;
  308. }
  309. }
  310. _rfModem->resetAvailable();
  311. }
  312. yield();
  313. }
  314. #endif // RFB_DIRECT
  315. void _rfbEnqueue(uint8_t * code, unsigned char times) {
  316. if (!_rfb_transmit) return;
  317. // rc-switch will repeat on its own
  318. #if RFB_DIRECT
  319. times = 1;
  320. #endif
  321. char buffer[RF_MESSAGE_SIZE];
  322. hexEncode(code, RF_MESSAGE_SIZE, buffer, sizeof(buffer));
  323. DEBUG_MSG_P(PSTR("[RF] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times);
  324. rfb_message_t message;
  325. memcpy(message.code, code, RF_MESSAGE_SIZE);
  326. message.times = times;
  327. _rfb_message_queue.push(message);
  328. }
  329. void _rfbSendQueued() {
  330. if (!_rfb_transmit) return;
  331. // Check if there is something in the queue
  332. if (_rfb_message_queue.empty()) return;
  333. static unsigned long last = 0;
  334. if (millis() - last < RF_SEND_DELAY) return;
  335. last = millis();
  336. // Pop the first message and send it
  337. rfb_message_t message = _rfb_message_queue.front();
  338. _rfb_message_queue.pop();
  339. _rfbSendImpl(message.code);
  340. // Push it to the stack again if we need to send it more than once
  341. if (message.times > 1) {
  342. message.times = message.times - 1;
  343. _rfb_message_queue.push(message);
  344. }
  345. yield();
  346. }
  347. bool _rfbCompare(const char * code1, const char * code2) {
  348. return strcmp(&code1[12], &code2[12]) == 0;
  349. }
  350. bool _rfbSameOnOff(unsigned char id) {
  351. return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
  352. }
  353. void _rfbParseCode(char * code) {
  354. // The payload may be a code in HEX format ([0-9A-Z]{18}) or
  355. // the code comma the number of times to transmit it.
  356. char * tok = strtok(code, ",");
  357. // Check if a switch is linked to that message
  358. unsigned char id;
  359. unsigned char status = 0;
  360. if (_rfbMatch(tok, id, status)) {
  361. if (status == 2) {
  362. relayToggle(id);
  363. } else {
  364. relayStatus(id, status == 1);
  365. }
  366. return;
  367. }
  368. uint8_t message[RF_MESSAGE_SIZE];
  369. if (hexDecode(tok, strlen(tok), message, sizeof(message))) {
  370. tok = strtok(nullptr, ",");
  371. uint8_t times = (tok != nullptr) ? atoi(tok) : 1;
  372. _rfbEnqueue(message, times);
  373. }
  374. }
  375. #if MQTT_SUPPORT
  376. void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) {
  377. if (type == MQTT_CONNECT_EVENT) {
  378. char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
  379. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
  380. mqttSubscribe(buffer);
  381. if (_rfb_transmit) {
  382. mqttSubscribe(MQTT_TOPIC_RFOUT);
  383. }
  384. #if !RFB_DIRECT
  385. mqttSubscribe(MQTT_TOPIC_RFRAW);
  386. #endif
  387. }
  388. if (type == MQTT_MESSAGE_EVENT) {
  389. // Match topic
  390. String t = mqttMagnitude((char *) topic);
  391. // Check if should go into learn mode
  392. if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
  393. _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
  394. if (_learnId >= relayCount()) {
  395. DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId);
  396. return;
  397. }
  398. _learnStatus = (char)payload[0] != '0';
  399. _rfbLearnImpl();
  400. return;
  401. }
  402. if (t.equals(MQTT_TOPIC_RFOUT)) {
  403. _rfbParseCode(payload);
  404. }
  405. #if !RFB_DIRECT
  406. if (t.equals(MQTT_TOPIC_RFRAW)) {
  407. _rfbParseRaw(payload);
  408. }
  409. #endif
  410. }
  411. }
  412. #endif // MQTT_SUPPORT
  413. #if API_SUPPORT
  414. void _rfbAPISetup() {
  415. apiRegister(MQTT_TOPIC_RFOUT,
  416. [](char * buffer, size_t len) {
  417. snprintf_P(buffer, len, PSTR("OK"));
  418. },
  419. [](const char * payload) {
  420. _rfbParseCode((char *) payload);
  421. }
  422. );
  423. apiRegister(MQTT_TOPIC_RFLEARN,
  424. [](char * buffer, size_t len) {
  425. snprintf_P(buffer, len, PSTR("OK"));
  426. },
  427. [](const char * payload) {
  428. // The payload must be the relayID plus the mode (0 or 1)
  429. char * tok = strtok((char *) payload, ",");
  430. if (NULL == tok) return;
  431. if (!isNumber(tok)) return;
  432. _learnId = atoi(tok);
  433. if (_learnId >= relayCount()) {
  434. DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId);
  435. return;
  436. }
  437. tok = strtok(NULL, ",");
  438. if (NULL == tok) return;
  439. _learnStatus = (char) tok[0] != '0';
  440. _rfbLearnImpl();
  441. }
  442. );
  443. #if !RFB_DIRECT
  444. apiRegister(MQTT_TOPIC_RFRAW,
  445. [](char * buffer, size_t len) {
  446. snprintf_P(buffer, len, PSTR("OK"));
  447. },
  448. [](const char * payload) {
  449. _rfbParseRaw((char *)payload);
  450. }
  451. );
  452. #endif
  453. }
  454. #endif // API_SUPPORT
  455. #if TERMINAL_SUPPORT
  456. void _rfbInitCommands() {
  457. terminalRegisterCommand(F("LEARN"), [](const terminal::CommandContext& ctx) {
  458. if (ctx.argc != 3) {
  459. terminalError(F("Wrong arguments"));
  460. return;
  461. }
  462. // 1st argument is relayID
  463. int id = ctx.argv[1].toInt();
  464. if (id >= relayCount()) {
  465. DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
  466. return;
  467. }
  468. // 2nd argument is status
  469. rfbLearn(id, (ctx.argv[2].toInt()) == 1);
  470. terminalOK();
  471. });
  472. terminalRegisterCommand(F("FORGET"), [](const terminal::CommandContext& ctx) {
  473. if (ctx.argc != 3) {
  474. terminalError(F("Wrong arguments"));
  475. return;
  476. }
  477. // 1st argument is relayID
  478. int id = ctx.argv[1].toInt();
  479. if (id >= relayCount()) {
  480. DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
  481. return;
  482. }
  483. // 2nd argument is status
  484. rfbForget(id, (ctx.argv[2].toInt()) == 1);
  485. terminalOK();
  486. });
  487. #if !RFB_DIRECT
  488. terminalRegisterCommand(F("RFB.WRITE"), [](const terminal::CommandContext& ctx) {
  489. if (ctx.argc != 2) return;
  490. uint8_t data[RF_MAX_MESSAGE_SIZE];
  491. size_t bytes = hexDecode(ctx.argv[1].c_str(), ctx.argv[1].length(), data, sizeof(data));
  492. if (bytes) {
  493. _rfbSendRaw(data, bytes);
  494. }
  495. });
  496. #endif
  497. }
  498. #endif // TERMINAL_SUPPORT
  499. // -----------------------------------------------------------------------------
  500. // PUBLIC
  501. // -----------------------------------------------------------------------------
  502. void rfbStore(unsigned char id, bool status, const char * code) {
  503. DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
  504. if (status) {
  505. setSetting({"rfbON", id}, code);
  506. } else {
  507. setSetting({"rfbOFF", id}, code);
  508. }
  509. }
  510. String rfbRetrieve(unsigned char id, bool status) {
  511. if (status) {
  512. return getSetting({"rfbON", id});
  513. } else {
  514. return getSetting({"rfbOFF", id});
  515. }
  516. }
  517. void rfbStatus(unsigned char id, bool status) {
  518. String value = rfbRetrieve(id, status);
  519. if (value.length() && !(value.length() & 1)) {
  520. uint8_t message[RF_MAX_MESSAGE_SIZE];
  521. size_t bytes = hexDecode(value.c_str(), value.length(), message, sizeof(message));
  522. if (bytes && !_rfbin) {
  523. if (value.length() == (RF_MESSAGE_SIZE * 2)) {
  524. _rfbEnqueue(message, _rfbSameOnOff(id) ? 1 : _rfb_repeat);
  525. } else {
  526. #if !RFB_DIRECT
  527. _rfbSendRaw(message, bytes);
  528. #endif
  529. }
  530. }
  531. }
  532. _rfbin = false;
  533. }
  534. void rfbLearn(unsigned char id, bool status) {
  535. _learnId = id;
  536. _learnStatus = status;
  537. _rfbLearnImpl();
  538. }
  539. void rfbForget(unsigned char id, bool status) {
  540. char key[RF_MAX_KEY_LENGTH] = {0};
  541. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  542. delSetting(key);
  543. // Websocket update
  544. #if WEB_SUPPORT
  545. wsPost([id](JsonObject& root) {
  546. _rfbWebSocketSendCodeArray(root, id, 1);
  547. });
  548. #endif
  549. }
  550. // -----------------------------------------------------------------------------
  551. // SETUP & LOOP
  552. // -----------------------------------------------------------------------------
  553. void rfbSetup() {
  554. #if MQTT_SUPPORT
  555. mqttRegister(_rfbMqttCallback);
  556. #endif
  557. #if API_SUPPORT
  558. _rfbAPISetup();
  559. #endif
  560. #if WEB_SUPPORT
  561. wsRegister()
  562. .onVisible(_rfbWebSocketOnVisible)
  563. .onConnected(_rfbWebSocketOnConnected)
  564. .onData(_rfbWebSocketOnData)
  565. .onAction(_rfbWebSocketOnAction)
  566. .onKeyCheck(_rfbWebSocketOnKeyCheck);
  567. #endif
  568. #if TERMINAL_SUPPORT
  569. _rfbInitCommands();
  570. #endif
  571. _rfb_repeat = getSetting("rfbRepeat", RF_SEND_TIMES);
  572. #if RFB_DIRECT
  573. const auto rx = getSetting("rfbRX", RFB_RX_PIN);
  574. const auto tx = getSetting("rfbTX", RFB_TX_PIN);
  575. _rfb_receive = gpioValid(rx);
  576. _rfb_transmit = gpioValid(tx);
  577. if (!_rfb_transmit && !_rfb_receive) {
  578. DEBUG_MSG_P(PSTR("[RF] Neither RX or TX are set\n"));
  579. return;
  580. }
  581. _rfModem = new RCSwitch();
  582. if (_rfb_receive) {
  583. _rfModem->enableReceive(rx);
  584. DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), rx);
  585. }
  586. if (_rfb_transmit) {
  587. _rfModem->enableTransmit(tx);
  588. _rfModem->setRepeatTransmit(_rfb_repeat);
  589. DEBUG_MSG_P(PSTR("[RF] RF transmitter on GPIO %u\n"), tx);
  590. }
  591. #else
  592. _rfb_receive = true;
  593. _rfb_transmit = true;
  594. #endif
  595. // Register loop only when properly configured
  596. espurnaRegisterLoop([]() -> void {
  597. _rfbReceiveImpl();
  598. _rfbSendQueued();
  599. });
  600. }
  601. #endif