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.

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