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.

436 lines
12 KiB

  1. /*
  2. ITEAD RF BRIDGE MODULE
  3. Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #ifdef ITEAD_SONOFF_RFBRIDGE
  6. #include <vector>
  7. #include <Ticker.h>
  8. // -----------------------------------------------------------------------------
  9. // DEFINITIONS
  10. // -----------------------------------------------------------------------------
  11. #define RF_MESSAGE_SIZE 9
  12. #define RF_CODE_START 0xAA
  13. #define RF_CODE_ACK 0xA0
  14. #define RF_CODE_LEARN 0xA1
  15. #define RF_CODE_LEARN_KO 0xA2
  16. #define RF_CODE_LEARN_OK 0xA3
  17. #define RF_CODE_RFIN 0xA4
  18. #define RF_CODE_RFOUT 0xA5
  19. #define RF_CODE_STOP 0x55
  20. // -----------------------------------------------------------------------------
  21. // GLOBALS TO THE MODULE
  22. // -----------------------------------------------------------------------------
  23. unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
  24. unsigned char _uartpos = 0;
  25. unsigned char _learnId = 0;
  26. bool _learnStatus = true;
  27. bool _rfbin = false;
  28. typedef struct {
  29. byte code[RF_MESSAGE_SIZE];
  30. byte times;
  31. } rfb_message_t;
  32. std::vector<rfb_message_t> _rfb_message_queue;
  33. Ticker _rfbTicker;
  34. // -----------------------------------------------------------------------------
  35. // PRIVATES
  36. // -----------------------------------------------------------------------------
  37. void _rfbWebSocketOnSend(JsonObject& root) {
  38. root["rfbVisible"] = 1;
  39. root["rfbCount"] = relayCount();
  40. JsonArray& rfb = root.createNestedArray("rfb");
  41. for (byte id=0; id<relayCount(); id++) {
  42. for (byte status=0; status<2; status++) {
  43. JsonObject& node = rfb.createNestedObject();
  44. node["id"] = id;
  45. node["status"] = status;
  46. node["data"] = rfbRetrieve(id, status == 1);
  47. }
  48. }
  49. }
  50. void _rfbWebSocketOnAction(const char * action, JsonObject& data) {
  51. if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
  52. if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
  53. if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
  54. }
  55. void _rfbAck() {
  56. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
  57. Serial.println();
  58. Serial.write(RF_CODE_START);
  59. Serial.write(RF_CODE_ACK);
  60. Serial.write(RF_CODE_STOP);
  61. Serial.flush();
  62. Serial.println();
  63. }
  64. void _rfbLearn() {
  65. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
  66. Serial.println();
  67. Serial.write(RF_CODE_START);
  68. Serial.write(RF_CODE_LEARN);
  69. Serial.write(RF_CODE_STOP);
  70. Serial.flush();
  71. Serial.println();
  72. #if WEB_SUPPORT
  73. char buffer[100];
  74. snprintf_P(buffer, sizeof(buffer), PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0);
  75. wsSend(buffer);
  76. #endif
  77. }
  78. void _rfbSend(byte * message) {
  79. Serial.println();
  80. Serial.write(RF_CODE_START);
  81. Serial.write(RF_CODE_RFOUT);
  82. for (unsigned char j=0; j<RF_MESSAGE_SIZE; j++) {
  83. Serial.write(message[j]);
  84. }
  85. Serial.write(RF_CODE_STOP);
  86. Serial.flush();
  87. Serial.println();
  88. }
  89. void _rfbSend() {
  90. // Check if there is something in the queue
  91. if (_rfb_message_queue.size() == 0) return;
  92. // Pop the first element
  93. rfb_message_t message = _rfb_message_queue.front();
  94. _rfb_message_queue.erase(_rfb_message_queue.begin());
  95. // Send the message
  96. _rfbSend(message.code);
  97. // If it should be further sent, push it to the stack again
  98. if (message.times > 1) {
  99. message.times = message.times - 1;
  100. _rfb_message_queue.push_back(message);
  101. }
  102. // if there are still messages in the queue...
  103. if (_rfb_message_queue.size() > 0) {
  104. _rfbTicker.once_ms(RF_SEND_DELAY, _rfbSend);
  105. }
  106. }
  107. void _rfbSend(byte * code, int times) {
  108. char buffer[RF_MESSAGE_SIZE];
  109. _rfbToChar(code, buffer);
  110. DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending MESSAGE '%s' %d time(s)\n"), buffer, times);
  111. rfb_message_t message;
  112. memcpy(message.code, code, RF_MESSAGE_SIZE);
  113. message.times = times;
  114. _rfb_message_queue.push_back(message);
  115. _rfbSend();
  116. }
  117. bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
  118. if (strlen(code) != 18) return false;
  119. bool found = false;
  120. String compareto = String(&code[12]);
  121. compareto.toUpperCase();
  122. DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str());
  123. for (unsigned char i=0; i<relayCount(); i++) {
  124. String code_on = rfbRetrieve(i, true);
  125. if (code_on.length() && code_on.endsWith(compareto)) {
  126. DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
  127. value = 1;
  128. found = true;
  129. }
  130. String code_off = rfbRetrieve(i, false);
  131. if (code_off.length() && code_off.endsWith(compareto)) {
  132. DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
  133. if (found) value = 2;
  134. found = true;
  135. }
  136. if (found) {
  137. relayID = i;
  138. return true;
  139. }
  140. }
  141. return false;
  142. }
  143. void _rfbDecode() {
  144. static unsigned long last = 0;
  145. if (millis() - last < RF_RECEIVE_DELAY) return;
  146. last = millis();
  147. byte action = _uartbuf[0];
  148. char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
  149. DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
  150. if (action == RF_CODE_LEARN_KO) {
  151. _rfbAck();
  152. DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
  153. #if WEB_SUPPORT
  154. wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
  155. #endif
  156. }
  157. if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
  158. #if MQTT_SUPPORT
  159. _rfbToChar(&_uartbuf[1], buffer);
  160. mqttSend(MQTT_TOPIC_RFIN, buffer);
  161. #endif
  162. _rfbAck();
  163. }
  164. if (action == RF_CODE_LEARN_OK) {
  165. DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
  166. rfbStore(_learnId, _learnStatus, buffer);
  167. // Websocket update
  168. #if WEB_SUPPORT
  169. char wsb[100];
  170. snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
  171. wsSend(wsb);
  172. #endif
  173. }
  174. if (action == RF_CODE_RFIN) {
  175. DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
  176. // Look for the code
  177. unsigned char id;
  178. unsigned char status = 0;
  179. if (_rfbMatch(buffer, id, status)) {
  180. _rfbin = true;
  181. if (status == 2) {
  182. relayToggle(id);
  183. } else {
  184. relayStatus(id, status == 1);
  185. }
  186. }
  187. }
  188. }
  189. void _rfbReceive() {
  190. static bool receiving = false;
  191. while (Serial.available()) {
  192. yield();
  193. byte c = Serial.read();
  194. //DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
  195. if (receiving) {
  196. if (c == RF_CODE_STOP && (_uartpos == 1 || _uartpos == 10)) {
  197. _rfbDecode();
  198. receiving = false;
  199. } else if (_uartpos < 10) {
  200. _uartbuf[_uartpos++] = c;
  201. } else {
  202. // wrong message, should have received a RF_CODE_STOP
  203. receiving = false;
  204. }
  205. } else if (c == RF_CODE_START) {
  206. _uartpos = 0;
  207. receiving = true;
  208. }
  209. }
  210. }
  211. bool _rfbCompare(const char * code1, const char * code2) {
  212. return strcmp(&code1[12], &code2[12]) == 0;
  213. }
  214. bool _rfbSameOnOff(unsigned char id) {
  215. return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
  216. }
  217. /*
  218. From an hexa char array ("A220EE...") to a byte array (half the size)
  219. */
  220. bool _rfbToArray(const char * in, byte * out) {
  221. if (strlen(in) != RF_MESSAGE_SIZE * 2) return false;
  222. char tmp[3] = {0};
  223. for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
  224. memcpy(tmp, &in[p*2], 2);
  225. out[p] = strtol(tmp, NULL, 16);
  226. }
  227. return true;
  228. }
  229. /*
  230. From a byte array to an hexa char array ("A220EE...", double the size)
  231. */
  232. bool _rfbToChar(byte * in, char * out) {
  233. for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
  234. sprintf_P(&out[p*2], PSTR("%02X"), in[p]);
  235. }
  236. return true;
  237. }
  238. #if MQTT_SUPPORT
  239. void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
  240. if (type == MQTT_CONNECT_EVENT) {
  241. char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
  242. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RFLEARN);
  243. mqttSubscribe(buffer);
  244. mqttSubscribe(MQTT_TOPIC_RFOUT);
  245. }
  246. if (type == MQTT_MESSAGE_EVENT) {
  247. // Match topic
  248. String t = mqttSubtopic((char *) topic);
  249. // Check if should go into learn mode
  250. if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
  251. _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
  252. if (_learnId >= relayCount()) {
  253. DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
  254. return;
  255. }
  256. _learnStatus = (char)payload[0] != '0';
  257. _rfbLearn();
  258. }
  259. if (t.equals(MQTT_TOPIC_RFOUT)) {
  260. // The payload may be a code in HEX format ([0-9A-Z]{18}) or
  261. // the code comma the number of times to transmit it.
  262. byte message[RF_MESSAGE_SIZE];
  263. char * tok = strtok((char *) payload, ",");
  264. // Check if a switch is linked to that message
  265. unsigned char id;
  266. unsigned char status = 0;
  267. if (_rfbMatch(tok, id, status)) {
  268. if (status == 2) {
  269. relayToggle(id);
  270. } else {
  271. relayStatus(id, status == 1);
  272. }
  273. return;
  274. }
  275. if (_rfbToArray(tok, message)) {
  276. tok = strtok(NULL, ",");
  277. byte times = (tok != NULL) ? atoi(tok) : 1;
  278. _rfbSend(message, times);
  279. }
  280. }
  281. }
  282. }
  283. #endif
  284. // -----------------------------------------------------------------------------
  285. // PUBLIC
  286. // -----------------------------------------------------------------------------
  287. void rfbStore(unsigned char id, bool status, const char * code) {
  288. DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
  289. char key[8] = {0};
  290. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  291. setSetting(key, code);
  292. }
  293. String rfbRetrieve(unsigned char id, bool status) {
  294. char key[8] = {0};
  295. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  296. return getSetting(key);
  297. }
  298. void rfbStatus(unsigned char id, bool status) {
  299. String value = rfbRetrieve(id, status);
  300. if (value.length() > 0) {
  301. bool same = _rfbSameOnOff(id);
  302. byte message[RF_MESSAGE_SIZE];
  303. _rfbToArray(value.c_str(), message);
  304. unsigned char times = RF_SEND_TIMES;
  305. if (same) times = _rfbin ? 0 : 1;
  306. _rfbSend(message, times);
  307. }
  308. }
  309. void rfbLearn(unsigned char id, bool status) {
  310. _learnId = id;
  311. _learnStatus = status;
  312. _rfbLearn();
  313. }
  314. void rfbForget(unsigned char id, bool status) {
  315. char key[8] = {0};
  316. snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id);
  317. delSetting(key);
  318. // Websocket update
  319. #if WEB_SUPPORT
  320. char wsb[100];
  321. snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
  322. wsSend(wsb);
  323. #endif
  324. }
  325. // -----------------------------------------------------------------------------
  326. // SETUP & LOOP
  327. // -----------------------------------------------------------------------------
  328. void rfbSetup() {
  329. #if MQTT_SUPPORT
  330. mqttRegister(_rfbMqttCallback);
  331. #endif
  332. #if WEB_SUPPORT
  333. wsOnSendRegister(_rfbWebSocketOnSend);
  334. wsOnActionRegister(_rfbWebSocketOnAction);
  335. #endif
  336. }
  337. void rfbLoop() {
  338. _rfbReceive();
  339. }
  340. #endif