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.

842 lines
24 KiB

7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
6 years ago
  1. /*
  2. RELAY MODULE
  3. Copyright (C) 2016-2018 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; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE or RELAY_TYPE_LATCHED
  14. unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED
  15. unsigned long delay_on; // Delay to turn relay ON
  16. unsigned long delay_off; // Delay to turn relay OFF
  17. unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
  18. unsigned long pulse_ms; // Pulse length in millis
  19. // Status variables
  20. bool current_status; // Holds the current (physical) status of the relay
  21. bool target_status; // Holds the target status
  22. unsigned long fw_start; // Flood window start time
  23. unsigned char fw_count; // Number of changes within the current flood window
  24. unsigned long change_time; // Scheduled time to change
  25. bool report; // Whether to report to own topic
  26. bool group_report; // Whether to report to group topic
  27. // Helping objects
  28. Ticker pulseTicker; // Holds the pulse back timer
  29. } relay_t;
  30. std::vector<relay_t> _relays;
  31. bool _relayRecursive = false;
  32. Ticker _relaySaveTicker;
  33. // -----------------------------------------------------------------------------
  34. // RELAY PROVIDERS
  35. // -----------------------------------------------------------------------------
  36. void _relayProviderStatus(unsigned char id, bool status) {
  37. // Check relay ID
  38. if (id >= _relays.size()) return;
  39. // Store new current status
  40. _relays[id].current_status = status;
  41. #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
  42. rfbStatus(id, status);
  43. #endif
  44. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  45. // Calculate mask
  46. unsigned char mask=0;
  47. for (unsigned char i=0; i<_relays.size(); i++) {
  48. if (_relays[i].current_status) mask = mask + (1 << i);
  49. }
  50. // Send it to F330
  51. Serial.flush();
  52. Serial.write(0xA0);
  53. Serial.write(0x04);
  54. Serial.write(mask);
  55. Serial.write(0xA1);
  56. Serial.flush();
  57. #endif
  58. #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
  59. // If the number of relays matches the number of light channels
  60. // assume each relay controls one channel.
  61. // If the number of relays is the number of channels plus 1
  62. // assume the first one controls all the channels and
  63. // the rest one channel each.
  64. // Otherwise every relay controls all channels.
  65. // TODO: this won't work with a mixed of dummy and real relays
  66. // but this option is not allowed atm (YANGNI)
  67. if (_relays.size() == lightChannels()) {
  68. lightState(id, status);
  69. lightState(true);
  70. } else if (_relays.size() == lightChannels() + 1) {
  71. if (id == 0) {
  72. lightState(status);
  73. } else {
  74. lightState(id-1, status);
  75. }
  76. } else {
  77. lightState(status);
  78. }
  79. lightUpdate(true, true);
  80. #endif
  81. #if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
  82. if (_relays[id].type == RELAY_TYPE_NORMAL) {
  83. digitalWrite(_relays[id].pin, status);
  84. } else if (_relays[id].type == RELAY_TYPE_INVERSE) {
  85. digitalWrite(_relays[id].pin, !status);
  86. } else if (_relays[id].type == RELAY_TYPE_LATCHED) {
  87. digitalWrite(_relays[id].pin, LOW);
  88. digitalWrite(_relays[id].reset_pin, LOW);
  89. if (status) {
  90. digitalWrite(_relays[id].pin, HIGH);
  91. } else {
  92. digitalWrite(_relays[id].reset_pin, HIGH);
  93. }
  94. delay(RELAY_LATCHING_PULSE);
  95. digitalWrite(_relays[id].pin, LOW);
  96. digitalWrite(_relays[id].reset_pin, LOW);
  97. }
  98. #endif
  99. }
  100. // -----------------------------------------------------------------------------
  101. // RELAY
  102. // -----------------------------------------------------------------------------
  103. void relayPulse(unsigned char id) {
  104. byte mode = _relays[id].pulse;
  105. if (mode == RELAY_PULSE_NONE) return;
  106. unsigned long ms = _relays[id].pulse_ms;
  107. if (ms == 0) return;
  108. bool status = relayStatus(id);
  109. bool pulseStatus = (mode == RELAY_PULSE_ON);
  110. if (pulseStatus == status) {
  111. _relays[id].pulseTicker.detach();
  112. } else {
  113. _relays[id].pulseTicker.once_ms(ms, relayToggle, id);
  114. }
  115. }
  116. bool relayStatus(unsigned char id, bool status, bool report, bool group_report) {
  117. if (id >= _relays.size()) return false;
  118. bool changed = false;
  119. if (_relays[id].current_status == status) {
  120. if (_relays[id].target_status != status) {
  121. DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled change cancelled\n"), id);
  122. _relays[id].target_status = status;
  123. _relays[id].report = false;
  124. _relays[id].group_report = false;
  125. changed = true;
  126. }
  127. // For RFBridge, keep sending the message even if the status is already the required
  128. #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
  129. rfbStatus(id, status);
  130. #endif
  131. // Update the pulse counter if the relay is already in the non-normal state (#454)
  132. relayPulse(id);
  133. } else {
  134. unsigned int current_time = millis();
  135. unsigned int fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW;
  136. unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off;
  137. _relays[id].fw_count++;
  138. _relays[id].change_time = current_time + delay;
  139. // If current_time is off-limits the floodWindow...
  140. if (current_time < _relays[id].fw_start || fw_end <= current_time) {
  141. // We reset the floodWindow
  142. _relays[id].fw_start = current_time;
  143. _relays[id].fw_count = 1;
  144. // If current_time is in the floodWindow and there have been too many requests...
  145. } else if (_relays[id].fw_count >= RELAY_FLOOD_CHANGES) {
  146. // We schedule the changes to the end of the floodWindow
  147. // unless it's already delayed beyond that point
  148. if (fw_end - delay > current_time) {
  149. _relays[id].change_time = fw_end;
  150. }
  151. }
  152. _relays[id].target_status = status;
  153. if (report) _relays[id].report = true;
  154. if (group_report) _relays[id].group_report = true;
  155. relaySync(id);
  156. DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled %s in %u ms\n"),
  157. id, status ? "ON" : "OFF",
  158. (_relays[id].change_time - current_time));
  159. changed = true;
  160. }
  161. return changed;
  162. }
  163. bool relayStatus(unsigned char id, bool status) {
  164. return relayStatus(id, status, true, true);
  165. }
  166. bool relayStatus(unsigned char id) {
  167. // Check relay ID
  168. if (id >= _relays.size()) return false;
  169. // Get status from storage
  170. return _relays[id].current_status;
  171. }
  172. void relaySync(unsigned char id) {
  173. // No sync if none or only one relay
  174. if (_relays.size() < 2) return;
  175. // Do not go on if we are comming from a previous sync
  176. if (_relayRecursive) return;
  177. // Flag sync mode
  178. _relayRecursive = true;
  179. byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt();
  180. bool status = _relays[id].target_status;
  181. // If RELAY_SYNC_SAME all relays should have the same state
  182. if (relaySync == RELAY_SYNC_SAME) {
  183. for (unsigned short i=0; i<_relays.size(); i++) {
  184. if (i != id) relayStatus(i, status);
  185. }
  186. // If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
  187. } else if (status) {
  188. if (relaySync != RELAY_SYNC_ANY) {
  189. for (unsigned short i=0; i<_relays.size(); i++) {
  190. if (i != id) relayStatus(i, false);
  191. }
  192. }
  193. // If ONLY_ONE and setting OFF we should set ON the other one
  194. } else {
  195. if (relaySync == RELAY_SYNC_ONE) {
  196. unsigned char i = (id + 1) % _relays.size();
  197. relayStatus(i, true);
  198. }
  199. }
  200. // Unflag sync mode
  201. _relayRecursive = false;
  202. }
  203. void relaySave() {
  204. unsigned char bit = 1;
  205. unsigned char mask = 0;
  206. for (unsigned int i=0; i < _relays.size(); i++) {
  207. if (relayStatus(i)) mask += bit;
  208. bit += bit;
  209. }
  210. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  211. DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
  212. EEPROM.commit();
  213. }
  214. void relayToggle(unsigned char id, bool report, bool group_report) {
  215. if (id >= _relays.size()) return;
  216. relayStatus(id, !relayStatus(id), report, group_report);
  217. }
  218. void relayToggle(unsigned char id) {
  219. relayToggle(id, true, true);
  220. }
  221. unsigned char relayCount() {
  222. return _relays.size();
  223. }
  224. unsigned char relayParsePayload(const char * payload) {
  225. // Payload could be "OFF", "ON", "TOGGLE"
  226. // or its number equivalents: 0, 1 or 2
  227. if (payload[0] == '0') return 0;
  228. if (payload[0] == '1') return 1;
  229. if (payload[0] == '2') return 2;
  230. // trim payload
  231. char * p = ltrim((char *)payload);
  232. // to lower
  233. unsigned int l = strlen(p);
  234. if (l>6) l=6;
  235. for (unsigned char i=0; i<l; i++) {
  236. p[i] = tolower(p[i]);
  237. }
  238. unsigned int value = 0xFF;
  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. }
  248. return value;
  249. }
  250. // BACKWARDS COMPATIBILITY
  251. void _relayBackwards() {
  252. byte relayMode = getSetting("relayMode", RELAY_BOOT_MODE).toInt();
  253. byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
  254. float relayPulseTime = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
  255. if (relayPulseMode == RELAY_PULSE_NONE) relayPulseTime = 0;
  256. for (unsigned int i=0; i<_relays.size(); i++) {
  257. if (!hasSetting("relayBoot", i)) setSetting("relayBoot", i, relayMode);
  258. if (!hasSetting("relayPulse", i)) setSetting("relayPulse", i, relayPulseMode);
  259. if (!hasSetting("relayTime", i)) setSetting("relayTime", i, relayPulseTime);
  260. }
  261. delSetting("relayMode");
  262. delSetting("relayPulseMode");
  263. delSetting("relayPulseTime");
  264. }
  265. void _relayBoot() {
  266. _relayRecursive = true;
  267. unsigned char bit = 1;
  268. bool trigger_save = false;
  269. // Get last statuses from EEPROM
  270. unsigned char mask = EEPROM.read(EEPROM_RELAY_STATUS);
  271. DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
  272. // Walk the relays
  273. bool status = false;
  274. for (unsigned int i=0; i<_relays.size(); i++) {
  275. unsigned char boot_mode = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
  276. DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode);
  277. switch (boot_mode) {
  278. case RELAY_BOOT_SAME:
  279. status = ((mask & bit) == bit);
  280. break;
  281. case RELAY_BOOT_TOGGLE:
  282. status = ((mask & bit) != bit);
  283. mask ^= bit;
  284. trigger_save = true;
  285. break;
  286. case RELAY_BOOT_ON:
  287. status = true;
  288. break;
  289. case RELAY_BOOT_OFF:
  290. default:
  291. status = false;
  292. break;
  293. }
  294. _relays[i].current_status = !status;
  295. _relays[i].target_status = status;
  296. _relays[i].change_time = millis();
  297. bit <<= 1;
  298. }
  299. // Save if there is any relay in the RELAY_BOOT_TOGGLE mode
  300. if (trigger_save) {
  301. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  302. EEPROM.commit();
  303. }
  304. _relayRecursive = false;
  305. }
  306. void _relayConfigure() {
  307. for (unsigned int i=0; i<_relays.size(); i++) {
  308. pinMode(_relays[i].pin, OUTPUT);
  309. if (_relays[i].type == RELAY_TYPE_LATCHED) pinMode(_relays[i].reset_pin, OUTPUT);
  310. _relays[i].pulse = getSetting("relayPulse", i, RELAY_PULSE_MODE).toInt();
  311. _relays[i].pulse_ms = 1000 * getSetting("relayTime", i, RELAY_PULSE_MODE).toFloat();
  312. }
  313. }
  314. //------------------------------------------------------------------------------
  315. // WEBSOCKETS
  316. //------------------------------------------------------------------------------
  317. #if WEB_SUPPORT
  318. void _relayWebSocketUpdate(JsonObject& root) {
  319. JsonArray& relay = root.createNestedArray("relayStatus");
  320. for (unsigned char i=0; i<relayCount(); i++) {
  321. relay.add(_relays[i].target_status);
  322. }
  323. }
  324. void _relayWebSocketOnStart(JsonObject& root) {
  325. if (relayCount() == 0) return;
  326. // Statuses
  327. _relayWebSocketUpdate(root);
  328. // Configuration
  329. JsonArray& config = root.createNestedArray("relayConfig");
  330. for (unsigned char i=0; i<relayCount(); i++) {
  331. JsonObject& line = config.createNestedObject();
  332. line["gpio"] = _relays[i].pin;
  333. line["type"] = _relays[i].type;
  334. line["reset"] = _relays[i].reset_pin;
  335. line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
  336. line["pulse"] = _relays[i].pulse;
  337. line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
  338. #if MQTT_SUPPORT
  339. line["group"] = getSetting("mqttGroup", i, "");
  340. line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
  341. #endif
  342. }
  343. if (relayCount() > 1) {
  344. root["multirelayVisible"] = 1;
  345. root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
  346. }
  347. root["relayVisible"] = 1;
  348. }
  349. void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  350. if (strcmp(action, "relay") != 0) return;
  351. if (data.containsKey("status")) {
  352. unsigned char value = relayParsePayload(data["status"]);
  353. if (value == 3) {
  354. wsSend(_relayWebSocketUpdate);
  355. } else if (value < 3) {
  356. unsigned int relayID = 0;
  357. if (data.containsKey("id")) {
  358. String value = data["id"];
  359. relayID = value.toInt();
  360. }
  361. // Action to perform
  362. if (value == 0) {
  363. relayStatus(relayID, false);
  364. } else if (value == 1) {
  365. relayStatus(relayID, true);
  366. } else if (value == 2) {
  367. relayToggle(relayID);
  368. }
  369. }
  370. }
  371. }
  372. void relaySetupWS() {
  373. wsOnSendRegister(_relayWebSocketOnStart);
  374. wsOnActionRegister(_relayWebSocketOnAction);
  375. wsOnAfterParseRegister(_relayConfigure);
  376. }
  377. #endif // WEB_SUPPORT
  378. //------------------------------------------------------------------------------
  379. // REST API
  380. //------------------------------------------------------------------------------
  381. #if WEB_SUPPORT
  382. void relaySetupAPI() {
  383. // API entry points (protected with apikey)
  384. for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
  385. char key[15];
  386. snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
  387. apiRegister(key,
  388. [relayID](char * buffer, size_t len) {
  389. snprintf_P(buffer, len, PSTR("%d"), relayStatus(relayID) ? 1 : 0);
  390. },
  391. [relayID](const char * payload) {
  392. unsigned char value = relayParsePayload(payload);
  393. if (value == 0xFF) {
  394. DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
  395. return;
  396. }
  397. if (value == 0) {
  398. relayStatus(relayID, false);
  399. } else if (value == 1) {
  400. relayStatus(relayID, true);
  401. } else if (value == 2) {
  402. relayToggle(relayID);
  403. }
  404. }
  405. );
  406. }
  407. }
  408. #endif // WEB_SUPPORT
  409. //------------------------------------------------------------------------------
  410. // MQTT
  411. //------------------------------------------------------------------------------
  412. #if MQTT_SUPPORT
  413. void relayMQTT(unsigned char id) {
  414. if (id >= _relays.size()) return;
  415. // Send state topic
  416. if (_relays[id].report) {
  417. _relays[id].report = false;
  418. mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
  419. }
  420. // Check group topic
  421. if (_relays[id].group_report) {
  422. _relays[id].group_report = false;
  423. String t = getSetting("mqttGroup", id, "");
  424. if (t.length() > 0) {
  425. bool status = relayStatus(id);
  426. if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status;
  427. mqttSendRaw(t.c_str(), status ? "1" : "0");
  428. }
  429. }
  430. }
  431. void relayMQTT() {
  432. for (unsigned int id=0; id < _relays.size(); id++) {
  433. mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0");
  434. }
  435. }
  436. void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) {
  437. // Action to perform
  438. if (value == 0) {
  439. relayStatus(id, false, mqttForward(), !is_group_topic);
  440. } else if (value == 1) {
  441. relayStatus(id, true, mqttForward(), !is_group_topic);
  442. } else if (value == 2) {
  443. relayToggle(id, true, true);
  444. }
  445. }
  446. void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  447. if (type == MQTT_CONNECT_EVENT) {
  448. // Send status on connect
  449. #if not HEARTBEAT_REPORT_RELAY
  450. relayMQTT();
  451. #endif
  452. // Subscribe to own /set topic
  453. char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
  454. snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
  455. mqttSubscribe(buffer);
  456. // Subscribe to group topics
  457. for (unsigned int i=0; i < _relays.size(); i++) {
  458. String t = getSetting("mqttGroup", i, "");
  459. if (t.length() > 0) mqttSubscribeRaw(t.c_str());
  460. }
  461. }
  462. if (type == MQTT_MESSAGE_EVENT) {
  463. // Check relay topic
  464. String t = mqttTopicKey((char *) topic);
  465. if (t.startsWith(MQTT_TOPIC_RELAY)) {
  466. // Get value
  467. unsigned char value = relayParsePayload(payload);
  468. if (value == 0xFF) return;
  469. // Get relay ID
  470. unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
  471. if (id >= relayCount()) {
  472. DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
  473. } else {
  474. relayStatusWrap(id, value, false);
  475. }
  476. return;
  477. }
  478. // Check group topics
  479. for (unsigned int i=0; i < _relays.size(); i++) {
  480. String t = getSetting("mqttGroup", i, "");
  481. if ((t.length() > 0) && t.equals(topic)) {
  482. unsigned char value = relayParsePayload(payload);
  483. if (value == 0xFF) return;
  484. if (value < 2) {
  485. if (getSetting("mqttGroupInv", i, 0).toInt() == 1) {
  486. value = 1 - value;
  487. }
  488. }
  489. DEBUG_MSG_P(PSTR("[RELAY] Matched group topic for relayID %d\n"), i);
  490. relayStatusWrap(i, value, true);
  491. }
  492. }
  493. }
  494. if (type == MQTT_DISCONNECT_EVENT)
  495. {
  496. if (MQTT_DISCONNECT_RELAY_DEFAULT == 1){
  497. for (unsigned int i=0; i < _relays.size(); i++){
  498. DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i);
  499. relayStatusWrap(i, false, false);
  500. }
  501. }
  502. }
  503. }
  504. void relaySetupMQTT() {
  505. mqttRegister(relayMQTTCallback);
  506. }
  507. #endif
  508. //------------------------------------------------------------------------------
  509. // InfluxDB
  510. //------------------------------------------------------------------------------
  511. #if INFLUXDB_SUPPORT
  512. void relayInfluxDB(unsigned char id) {
  513. if (id >= _relays.size()) return;
  514. idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
  515. }
  516. #endif
  517. //------------------------------------------------------------------------------
  518. // Settings
  519. //------------------------------------------------------------------------------
  520. #if TERMINAL_SUPPORT
  521. void _relayInitCommands() {
  522. settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
  523. if (e->argc < 2) {
  524. DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
  525. }
  526. int id = String(e->argv[1]).toInt();
  527. if (e->argc > 2) {
  528. int value = String(e->argv[2]).toInt();
  529. if (value == 2) {
  530. relayToggle(id);
  531. } else {
  532. relayStatus(id, value == 1);
  533. }
  534. }
  535. DEBUG_MSG_P(PSTR("Status: %s\n"), relayStatus(id) ? "true" : "false");
  536. DEBUG_MSG_P(PSTR("+OK\n"));
  537. });
  538. }
  539. #endif // TERMINAL_SUPPORT
  540. //------------------------------------------------------------------------------
  541. // Setup
  542. //------------------------------------------------------------------------------
  543. void relaySetup() {
  544. // Dummy relays for AI Light, Magic Home LED Controller, H801,
  545. // Sonoff Dual and Sonoff RF Bridge
  546. #ifdef DUMMY_RELAY_COUNT
  547. for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
  548. _relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
  549. }
  550. #else
  551. #ifdef RELAY1_PIN
  552. _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
  553. #endif
  554. #ifdef RELAY2_PIN
  555. _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
  556. #endif
  557. #ifdef RELAY3_PIN
  558. _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
  559. #endif
  560. #ifdef RELAY4_PIN
  561. _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
  562. #endif
  563. #ifdef RELAY5_PIN
  564. _relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF });
  565. #endif
  566. #ifdef RELAY6_PIN
  567. _relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF });
  568. #endif
  569. #ifdef RELAY7_PIN
  570. _relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF });
  571. #endif
  572. #ifdef RELAY8_PIN
  573. _relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF });
  574. #endif
  575. #endif
  576. _relayBackwards();
  577. _relayConfigure();
  578. _relayBoot();
  579. relayLoop();
  580. espurnaRegisterLoop(relayLoop);
  581. #if WEB_SUPPORT
  582. relaySetupAPI();
  583. relaySetupWS();
  584. #endif
  585. #if MQTT_SUPPORT
  586. relaySetupMQTT();
  587. #endif
  588. #if TERMINAL_SUPPORT
  589. _relayInitCommands();
  590. #endif
  591. DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
  592. }
  593. void relayLoop(void) {
  594. unsigned char id;
  595. for (id = 0; id < _relays.size(); id++) {
  596. unsigned int current_time = millis();
  597. bool status = _relays[id].target_status;
  598. if ((_relays[id].current_status != status)
  599. && (current_time >= _relays[id].change_time)) {
  600. DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
  601. // Call the provider to perform the action
  602. _relayProviderStatus(id, status);
  603. // Send to Broker
  604. #if BROKER_SUPPORT
  605. brokerPublish(MQTT_TOPIC_RELAY, id, status ? "1" : "0");
  606. #endif
  607. // Send MQTT
  608. #if MQTT_SUPPORT
  609. relayMQTT(id);
  610. #endif
  611. if (!_relayRecursive) {
  612. relayPulse(id);
  613. _relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
  614. #if WEB_SUPPORT
  615. wsSend(_relayWebSocketUpdate);
  616. #endif
  617. }
  618. #if DOMOTICZ_SUPPORT
  619. domoticzSendRelay(id);
  620. #endif
  621. #if INFLUXDB_SUPPORT
  622. relayInfluxDB(id);
  623. #endif
  624. #if THINGSPEAK_SUPPORT
  625. tspkEnqueueRelay(id, status);
  626. tspkFlush();
  627. #endif
  628. // Flag relay-based LEDs to update status
  629. ledUpdate(true);
  630. _relays[id].report = false;
  631. _relays[id].group_report = false;
  632. }
  633. }
  634. }