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.

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