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.

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