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.

723 lines
20 KiB

  1. /*
  2. RELAY MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include <EEPROM.h>
  6. #include <Ticker.h>
  7. #include <ArduinoJson.h>
  8. #include <vector>
  9. #include <functional>
  10. typedef struct {
  11. // Configuration variables
  12. unsigned char pin; // GPIO pin for the relay
  13. unsigned char type;
  14. unsigned char reset_pin;
  15. unsigned char led;
  16. unsigned long delay_on;
  17. unsigned long delay_off;
  18. // Status variables
  19. bool current_status;
  20. bool target_status;
  21. unsigned int fw_start;
  22. unsigned char fw_count;
  23. unsigned int change_time;
  24. bool report;
  25. bool group_report;
  26. // Helping objects
  27. Ticker pulseTicker;
  28. } relay_t;
  29. std::vector<relay_t> _relays;
  30. bool recursive = false;
  31. Ticker _relaySaveTicker;
  32. // -----------------------------------------------------------------------------
  33. // RELAY PROVIDERS
  34. // -----------------------------------------------------------------------------
  35. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  36. #endif
  37. void _relayProviderStatus(unsigned char id, bool status) {
  38. // Check relay ID
  39. if (id >= _relays.size()) return;
  40. // Store new current status
  41. _relays[id].current_status = status;
  42. #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
  43. rfbStatus(id, status);
  44. #endif
  45. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  46. // Calculate mask
  47. unsigned char mask=0;
  48. for (unsigned char i=0; i<_relays.size(); i++) {
  49. if (_relays[i].current_status) mask = mask + (1 << i);
  50. }
  51. // Send it to F330
  52. Serial.flush();
  53. Serial.write(0xA0);
  54. Serial.write(0x04);
  55. Serial.write(mask);
  56. Serial.write(0xA1);
  57. Serial.flush();
  58. #endif
  59. #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
  60. lightState(status);
  61. lightUpdate(true, true);
  62. #endif
  63. #if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
  64. if (_relays[id].type == RELAY_TYPE_NORMAL) {
  65. digitalWrite(_relays[id].pin, status);
  66. } else if (_relays[id].type == RELAY_TYPE_INVERSE) {
  67. digitalWrite(_relays[id].pin, !status);
  68. } else if (_relays[id].type == RELAY_TYPE_LATCHED) {
  69. digitalWrite(_relays[id].pin, LOW);
  70. digitalWrite(_relays[id].reset_pin, LOW);
  71. if (status) {
  72. digitalWrite(_relays[id].pin, HIGH);
  73. } else {
  74. digitalWrite(_relays[id].reset_pin, HIGH);
  75. }
  76. delay(RELAY_LATCHING_PULSE);
  77. digitalWrite(_relays[id].pin, LOW);
  78. digitalWrite(_relays[id].reset_pin, LOW);
  79. }
  80. #endif
  81. }
  82. // -----------------------------------------------------------------------------
  83. // RELAY
  84. // -----------------------------------------------------------------------------
  85. void relayPulse(unsigned char id) {
  86. byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
  87. if (relayPulseMode == RELAY_PULSE_NONE) return;
  88. long relayPulseTime = 1000.0 * getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
  89. if (relayPulseTime == 0) return;
  90. bool status = relayStatus(id);
  91. bool pulseStatus = (relayPulseMode == RELAY_PULSE_ON);
  92. if (pulseStatus == status) {
  93. _relays[id].pulseTicker.detach();
  94. return;
  95. }
  96. _relays[id].pulseTicker.once_ms(relayPulseTime, relayToggle, id);
  97. }
  98. unsigned int relayPulseMode() {
  99. unsigned int value = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
  100. return value;
  101. }
  102. void relayPulseMode(unsigned int value) {
  103. setSetting("relayPulseMode", value);
  104. #if WEB_SUPPORT
  105. char message[20];
  106. snprintf_P(message, sizeof(message), PSTR("{\"relayPulseMode\": %d}"), value);
  107. wsSend(message);
  108. #endif
  109. }
  110. void relayPulseToggle() {
  111. unsigned int value = relayPulseMode();
  112. value = (value == RELAY_PULSE_NONE) ? RELAY_PULSE_OFF : RELAY_PULSE_NONE;
  113. relayPulseMode(value);
  114. }
  115. bool relayStatus(unsigned char id, bool status, bool report, bool group_report) {
  116. if (id >= _relays.size()) return false;
  117. bool changed = false;
  118. if (_relays[id].current_status == status) {
  119. if (_relays[id].target_status != status) {
  120. DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled change cancelled\n"), id);
  121. _relays[id].target_status = status;
  122. _relays[id].report = false;
  123. _relays[id].group_report = false;
  124. changed = true;
  125. }
  126. } else {
  127. unsigned int current_time = millis();
  128. unsigned int fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW;
  129. unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off;
  130. _relays[id].fw_count++;
  131. _relays[id].change_time = current_time + delay;
  132. // If current_time is off-limits the floodWindow...
  133. if (current_time < _relays[id].fw_start || fw_end <= current_time) {
  134. // We reset the floodWindow
  135. _relays[id].fw_start = current_time;
  136. _relays[id].fw_count = 1;
  137. // If current_time is in the floodWindow and there have been too many requests...
  138. } else if (_relays[id].fw_count >= RELAY_FLOOD_CHANGES) {
  139. // We schedule the changes to the end of the floodWindow
  140. // unless it's already delayed beyond that point
  141. if (fw_end - delay > current_time) {
  142. _relays[id].change_time = fw_end;
  143. }
  144. }
  145. _relays[id].target_status = status;
  146. if (report) _relays[id].report = true;
  147. if (group_report) _relays[id].group_report = true;
  148. DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled %s in %u ms\n"),
  149. id, status ? "ON" : "OFF",
  150. (_relays[id].change_time - current_time));
  151. changed = true;
  152. }
  153. return changed;
  154. }
  155. bool relayStatus(unsigned char id, bool status) {
  156. return relayStatus(id, status, true, true);
  157. }
  158. bool relayStatus(unsigned char id) {
  159. // Check relay ID
  160. if (id >= _relays.size()) return false;
  161. // GEt status from storage
  162. return _relays[id].current_status;
  163. }
  164. void relaySync(unsigned char id) {
  165. if (_relays.size() > 1) {
  166. recursive = true;
  167. byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt();
  168. bool status = relayStatus(id);
  169. // If RELAY_SYNC_SAME all relays should have the same state
  170. if (relaySync == RELAY_SYNC_SAME) {
  171. for (unsigned short i=0; i<_relays.size(); i++) {
  172. if (i != id) relayStatus(i, status);
  173. }
  174. // If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
  175. } else if (status) {
  176. if (relaySync != RELAY_SYNC_ANY) {
  177. for (unsigned short i=0; i<_relays.size(); i++) {
  178. if (i != id) relayStatus(i, false);
  179. }
  180. }
  181. // If ONLY_ONE and setting OFF we should set ON the other one
  182. } else {
  183. if (relaySync == RELAY_SYNC_ONE) {
  184. unsigned char i = (id + 1) % _relays.size();
  185. relayStatus(i, true);
  186. }
  187. }
  188. recursive = false;
  189. }
  190. }
  191. void relaySave() {
  192. unsigned char bit = 1;
  193. unsigned char mask = 0;
  194. for (unsigned int i=0; i < _relays.size(); i++) {
  195. if (relayStatus(i)) mask += bit;
  196. bit += bit;
  197. }
  198. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  199. DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
  200. EEPROM.commit();
  201. }
  202. void relayRetrieve(bool invert) {
  203. recursive = true;
  204. unsigned char bit = 1;
  205. unsigned char mask = invert ? ~EEPROM.read(EEPROM_RELAY_STATUS) : EEPROM.read(EEPROM_RELAY_STATUS);
  206. DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
  207. for (unsigned int id=0; id < _relays.size(); id++) {
  208. _relays[id].target_status = ((mask & bit) == bit);
  209. _relays[id].report = true;
  210. _relays[id].group_report = false; // Don't do group report on start
  211. bit += bit;
  212. }
  213. if (invert) {
  214. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  215. EEPROM.commit();
  216. }
  217. recursive = false;
  218. }
  219. void relayToggle(unsigned char id, bool report, bool group_report) {
  220. if (id >= _relays.size()) return;
  221. relayStatus(id, !relayStatus(id), report, group_report);
  222. }
  223. void relayToggle(unsigned char id) {
  224. relayToggle(id, true, true);
  225. }
  226. unsigned char relayCount() {
  227. return _relays.size();
  228. }
  229. unsigned char relayParsePayload(const char * payload) {
  230. // Payload could be "OFF", "ON", "TOGGLE"
  231. // or its number equivalents: 0, 1 or 2
  232. // trim payload
  233. char * p = ltrim((char *)payload);
  234. // to lower
  235. for (unsigned char i=0; i<strlen(p); i++) {
  236. p[i] = tolower(p[i]);
  237. }
  238. unsigned int value;
  239. if (strcmp(p, "off") == 0) {
  240. value = 0;
  241. } else if (strcmp(p, "on") == 0) {
  242. value = 1;
  243. } else if (strcmp(p, "toggle") == 0) {
  244. value = 2;
  245. } else if (strcmp(p, "query") == 0) {
  246. value = 3;
  247. } else {
  248. value = p[0] - '0';
  249. }
  250. if (0 <= value && value <=3) return value;
  251. return 0xFF;
  252. }
  253. //------------------------------------------------------------------------------
  254. // WEBSOCKETS
  255. //------------------------------------------------------------------------------
  256. #if WEB_SUPPORT
  257. void _relayWebSocketUpdate() {
  258. DynamicJsonBuffer jsonBuffer;
  259. JsonObject& root = jsonBuffer.createObject();
  260. // Statuses
  261. JsonArray& relay = root.createNestedArray("relayStatus");
  262. for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
  263. relay.add(relayStatus(relayID));
  264. }
  265. String output;
  266. root.printTo(output);
  267. wsSend((char *) output.c_str());
  268. }
  269. void _relayWebSocketOnSend(JsonObject& root) {
  270. // Statuses
  271. JsonArray& relay = root.createNestedArray("relayStatus");
  272. for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
  273. relay.add(relayStatus(relayID));
  274. }
  275. // Configuration
  276. root["relayMode"] = getSetting("relayMode", RELAY_MODE);
  277. root["relayPulseMode"] = getSetting("relayPulseMode", RELAY_PULSE_MODE);
  278. root["relayPulseTime"] = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
  279. if (relayCount() > 1) {
  280. root["multirelayVisible"] = 1;
  281. root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
  282. }
  283. // Group topics
  284. #if MQTT_SUPPORT
  285. JsonArray& groups = root.createNestedArray("relayGroups");
  286. for (unsigned char i=0; i<relayCount(); i++) {
  287. JsonObject& group = groups.createNestedObject();
  288. group["mqttGroup"] = getSetting("mqttGroup", i, "");
  289. group["mqttGroupInv"] = getSetting("mqttGroupInv", i, 0).toInt() == 1;
  290. }
  291. #endif
  292. }
  293. void _relayWebSocketOnAction(const char * action, JsonObject& data) {
  294. if (strcmp(action, "relay") != 0) return;
  295. if (data.containsKey("status")) {
  296. unsigned char value = relayParsePayload(data["status"]);
  297. if (value == 3) {
  298. _relayWebSocketUpdate();
  299. } else if (value < 3) {
  300. unsigned int relayID = 0;
  301. if (data.containsKey("id")) {
  302. String value = data["id"];
  303. relayID = value.toInt();
  304. }
  305. // Action to perform
  306. if (value == 0) {
  307. relayStatus(relayID, false);
  308. } else if (value == 1) {
  309. relayStatus(relayID, true);
  310. } else if (value == 2) {
  311. relayToggle(relayID);
  312. }
  313. }
  314. }
  315. }
  316. void relaySetupWS() {
  317. wsOnSendRegister(_relayWebSocketOnSend);
  318. wsOnActionRegister(_relayWebSocketOnAction);
  319. }
  320. #endif // WEB_SUPPORT
  321. //------------------------------------------------------------------------------
  322. // REST API
  323. //------------------------------------------------------------------------------
  324. #if WEB_SUPPORT
  325. void relaySetupAPI() {
  326. // API entry points (protected with apikey)
  327. for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
  328. char url[15];
  329. snprintf_P(url, sizeof(url), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
  330. char key[10];
  331. snprintf_P(key, sizeof(key), PSTR("%s%d"), MQTT_TOPIC_RELAY, relayID);
  332. apiRegister(url, key,
  333. [relayID](char * buffer, size_t len) {
  334. snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
  335. },
  336. [relayID](const char * payload) {
  337. unsigned char value = relayParsePayload(payload);
  338. if (value == 0xFF) {
  339. DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
  340. return;
  341. }
  342. if (value == 0) {
  343. relayStatus(relayID, false);
  344. } else if (value == 1) {
  345. relayStatus(relayID, true);
  346. } else if (value == 2) {
  347. relayToggle(relayID);
  348. }
  349. }
  350. );
  351. }
  352. }
  353. #endif // WEB_SUPPORT
  354. //------------------------------------------------------------------------------
  355. // MQTT
  356. //------------------------------------------------------------------------------
  357. #if MQTT_SUPPORT
  358. void relayMQTT(unsigned char id) {
  359. if (id >= _relays.size()) return;
  360. // Send state topic
  361. if (_relays[id].report) {
  362. _relays[id].report = false;
  363. mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
  364. }
  365. // Check group topic
  366. if (_relays[id].group_report) {
  367. _relays[id].group_report = false;
  368. String t = getSetting("mqttGroup", id, "");
  369. if (t.length() > 0) {
  370. bool status = relayStatus(id);
  371. if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status;
  372. mqttSendRaw(t.c_str(), status ? "1" : "0");
  373. }
  374. }
  375. }
  376. void relayMQTT() {
  377. for (unsigned int id=0; id < _relays.size(); id++) {
  378. mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
  379. }
  380. }
  381. void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  382. if (type == MQTT_CONNECT_EVENT) {
  383. // Send status on connect
  384. #if not HEARTBEAT_REPORT_RELAY
  385. relayMQTT();
  386. #endif
  387. // Subscribe to own /set topic
  388. char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
  389. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
  390. mqttSubscribe(buffer);
  391. // Subscribe to group topics
  392. for (unsigned int i=0; i < _relays.size(); i++) {
  393. String t = getSetting("mqttGroup", i, "");
  394. if (t.length() > 0) mqttSubscribeRaw(t.c_str());
  395. }
  396. }
  397. if (type == MQTT_MESSAGE_EVENT) {
  398. // Get relay
  399. unsigned int relayID;
  400. bool is_group_topic = false;
  401. // Get value
  402. unsigned char value = relayParsePayload(payload);
  403. if (value == 0xFF) {
  404. DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
  405. return;
  406. }
  407. // Check group topics
  408. for (unsigned int i=0; i < _relays.size(); i++) {
  409. String t = getSetting("mqttGroup", i, "");
  410. if (t.equals(topic)) {
  411. is_group_topic = true;
  412. relayID = i;
  413. if (getSetting("mqttGroupInv", relayID, 0).toInt() == 1) {
  414. if (value < 2) value = 1 - value;
  415. }
  416. DEBUG_MSG_P(PSTR("[RELAY] Matched group topic for relayID %d\n"), relayID);
  417. break;
  418. }
  419. }
  420. // Not group topic, look for own topic
  421. if (!is_group_topic) {
  422. // Match topic
  423. String t = mqttSubtopic((char *) topic);
  424. if (!t.startsWith(MQTT_TOPIC_RELAY)) return;
  425. // Pulse topic
  426. if (t.endsWith("pulse")) {
  427. relayPulseMode(value);
  428. return;
  429. }
  430. // Get relay ID
  431. relayID = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
  432. if (relayID >= relayCount()) {
  433. DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), relayID);
  434. return;
  435. }
  436. }
  437. // Action to perform
  438. if (value == 0) {
  439. relayStatus(relayID, false, mqttForward(), !is_group_topic);
  440. } else if (value == 1) {
  441. relayStatus(relayID, true, mqttForward(), !is_group_topic);
  442. } else if (value == 2) {
  443. relayToggle(relayID, true, true);
  444. }
  445. }
  446. }
  447. void relaySetupMQTT() {
  448. mqttRegister(relayMQTTCallback);
  449. }
  450. #endif
  451. //------------------------------------------------------------------------------
  452. // InfluxDB
  453. //------------------------------------------------------------------------------
  454. #if INFLUXDB_SUPPORT
  455. void relayInfluxDB(unsigned char id) {
  456. if (id >= _relays.size()) return;
  457. char buffer[10];
  458. snprintf_P(buffer, sizeof(buffer), PSTR("%s,id=%d"), MQTT_TOPIC_RELAY, id);
  459. idbSend(buffer, relayStatus(id) ? "1" : "0");
  460. }
  461. #endif
  462. //------------------------------------------------------------------------------
  463. // Setup
  464. //------------------------------------------------------------------------------
  465. void relaySetup() {
  466. // Dummy relays for AI Light, Magic Home LED Controller, H801,
  467. // Sonoff Dual and Sonoff RF Bridge
  468. #ifdef DUMMY_RELAY_COUNT
  469. for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
  470. _relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
  471. }
  472. #else
  473. #ifdef RELAY1_PIN
  474. _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_LED, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
  475. #endif
  476. #ifdef RELAY2_PIN
  477. _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_LED, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
  478. #endif
  479. #ifdef RELAY3_PIN
  480. _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_LED, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
  481. #endif
  482. #ifdef RELAY4_PIN
  483. _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_LED, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
  484. #endif
  485. #endif
  486. byte relayMode = getSetting("relayMode", RELAY_MODE).toInt();
  487. for (unsigned int i=0; i < _relays.size(); i++) {
  488. pinMode(_relays[i].pin, OUTPUT);
  489. if (relayMode == RELAY_MODE_OFF) relayStatus(i, false);
  490. if (relayMode == RELAY_MODE_ON) relayStatus(i, true);
  491. }
  492. if (relayMode == RELAY_MODE_SAME) relayRetrieve(false);
  493. if (relayMode == RELAY_MODE_TOOGLE) relayRetrieve(true);
  494. relayLoop();
  495. #if WEB_SUPPORT
  496. relaySetupAPI();
  497. relaySetupWS();
  498. #endif
  499. #if MQTT_SUPPORT
  500. relaySetupMQTT();
  501. #endif
  502. DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
  503. }
  504. void relayLoop(void) {
  505. unsigned char id;
  506. for (id = 0; id < _relays.size(); id++) {
  507. unsigned int current_time = millis();
  508. bool status = _relays[id].target_status;
  509. if ((_relays[id].current_status != status)
  510. && (current_time >= _relays[id].change_time)) {
  511. DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
  512. // Call the provider to perform the action
  513. _relayProviderStatus(id, status);
  514. // Change the binded LED if any
  515. if (_relays[id].led > 0) {
  516. ledStatus(_relays[id].led - 1, status);
  517. }
  518. // Send MQTT
  519. #if MQTT_SUPPORT
  520. relayMQTT(id);
  521. #endif
  522. if (!recursive) {
  523. relayPulse(id);
  524. relaySync(id);
  525. _relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
  526. #if WEB_SUPPORT
  527. _relayWebSocketUpdate();
  528. #endif
  529. }
  530. #if DOMOTICZ_SUPPORT
  531. domoticzSendRelay(id);
  532. #endif
  533. #if INFLUXDB_SUPPORT
  534. relayInfluxDB(id);
  535. #endif
  536. _relays[id].report = false;
  537. _relays[id].group_report = false;
  538. }
  539. }
  540. }