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.

550 lines
14 KiB

7 years ago
  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. unsigned char pin;
  12. bool reverse;
  13. unsigned char led;
  14. unsigned int floodWindowStart;
  15. unsigned char floodWindowChanges;
  16. unsigned int scheduledStatusTime;
  17. bool scheduledStatus;
  18. bool scheduledReport;
  19. Ticker pulseTicker;
  20. } relay_t;
  21. std::vector<relay_t> _relays;
  22. bool recursive = false;
  23. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  24. unsigned char _dual_status = 0;
  25. #endif
  26. // -----------------------------------------------------------------------------
  27. // RELAY PROVIDERS
  28. // -----------------------------------------------------------------------------
  29. void relayProviderStatus(unsigned char id, bool status) {
  30. if (id >= _relays.size()) return;
  31. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  32. _dual_status ^= (1 << id);
  33. Serial.flush();
  34. Serial.write(0xA0);
  35. Serial.write(0x04);
  36. Serial.write(_dual_status);
  37. Serial.write(0xA1);
  38. Serial.flush();
  39. #endif
  40. #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
  41. lightState(status);
  42. #endif
  43. #if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
  44. digitalWrite(_relays[id].pin, _relays[id].reverse ? !status : status);
  45. #endif
  46. }
  47. bool relayProviderStatus(unsigned char id) {
  48. if (id >= _relays.size()) return false;
  49. #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
  50. return ((_dual_status & (1 << id)) > 0);
  51. #endif
  52. #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
  53. return lightState();
  54. #endif
  55. #if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
  56. bool status = (digitalRead(_relays[id].pin) == HIGH);
  57. return _relays[id].reverse ? !status : status;
  58. #endif
  59. }
  60. // -----------------------------------------------------------------------------
  61. // RELAY
  62. // -----------------------------------------------------------------------------
  63. String relayString() {
  64. DynamicJsonBuffer jsonBuffer;
  65. JsonObject& root = jsonBuffer.createObject();
  66. JsonArray& relay = root.createNestedArray("relayStatus");
  67. for (unsigned char i=0; i<relayCount(); i++) {
  68. relay.add(relayStatus(i));
  69. }
  70. String output;
  71. root.printTo(output);
  72. return output;
  73. }
  74. bool relayStatus(unsigned char id) {
  75. return relayProviderStatus(id);
  76. }
  77. void relayPulse(unsigned char id) {
  78. byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
  79. if (relayPulseMode == RELAY_PULSE_NONE) return;
  80. bool status = relayStatus(id);
  81. bool pulseStatus = (relayPulseMode == RELAY_PULSE_ON);
  82. if (pulseStatus == status) {
  83. _relays[id].pulseTicker.detach();
  84. return;
  85. }
  86. _relays[id].pulseTicker.once(
  87. getSetting("relayPulseTime", RELAY_PULSE_TIME).toInt(),
  88. relayToggle,
  89. id
  90. );
  91. }
  92. unsigned int relayPulseMode() {
  93. unsigned int value = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
  94. return value;
  95. }
  96. void relayPulseMode(unsigned int value, bool report) {
  97. setSetting("relayPulseMode", value);
  98. /*
  99. if (report) {
  100. char topic[strlen(MQTT_TOPIC_RELAY) + 10];
  101. sprintf(topic, "%s/pulse", MQTT_TOPIC_RELAY);
  102. char value[2];
  103. sprintf(value, "%d", value);
  104. mqttSend(topic, value);
  105. }
  106. */
  107. char message[20];
  108. sprintf(message, "{\"relayPulseMode\": %d}", value);
  109. wsSend(message);
  110. }
  111. void relayPulseMode(unsigned int value) {
  112. relayPulseMode(value, true);
  113. }
  114. void relayPulseToggle() {
  115. unsigned int value = relayPulseMode();
  116. value = (value == RELAY_PULSE_NONE) ? RELAY_PULSE_OFF : RELAY_PULSE_NONE;
  117. relayPulseMode(value);
  118. }
  119. bool relayStatus(unsigned char id, bool status, bool report) {
  120. if (id >= _relays.size()) return false;
  121. bool changed = false;
  122. if (relayStatus(id) != status) {
  123. unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW;
  124. unsigned int currentTime = millis();
  125. _relays[id].floodWindowChanges++;
  126. _relays[id].scheduledStatusTime = currentTime;
  127. if (currentTime >= floodWindowEnd || currentTime < _relays[id].floodWindowStart) {
  128. _relays[id].floodWindowStart = currentTime;
  129. _relays[id].floodWindowChanges = 1;
  130. } else if (_relays[id].floodWindowChanges >= RELAY_FLOOD_CHANGES) {
  131. _relays[id].scheduledStatusTime = floodWindowEnd;
  132. }
  133. _relays[id].scheduledStatus = status;
  134. _relays[id].scheduledReport = (report ? true : _relays[id].scheduledReport);
  135. DEBUG_MSG_P(PSTR("[RELAY] Scheduled %d => %s in %u ms\n"),
  136. id, status ? "ON" : "OFF",
  137. (_relays[id].scheduledStatusTime - currentTime));
  138. changed = true;
  139. }
  140. return changed;
  141. }
  142. bool relayStatus(unsigned char id, bool status) {
  143. return relayStatus(id, status, true);
  144. }
  145. void relaySync(unsigned char id) {
  146. if (_relays.size() > 1) {
  147. recursive = true;
  148. byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt();
  149. bool status = relayStatus(id);
  150. // If RELAY_SYNC_SAME all relays should have the same state
  151. if (relaySync == RELAY_SYNC_SAME) {
  152. for (unsigned short i=0; i<_relays.size(); i++) {
  153. if (i != id) relayStatus(i, status);
  154. }
  155. // If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
  156. } else if (status) {
  157. if (relaySync != RELAY_SYNC_ANY) {
  158. for (unsigned short i=0; i<_relays.size(); i++) {
  159. if (i != id) relayStatus(i, false);
  160. }
  161. }
  162. // If ONLY_ONE and setting OFF we should set ON the other one
  163. } else {
  164. if (relaySync == RELAY_SYNC_ONE) {
  165. unsigned char i = (id + 1) % _relays.size();
  166. relayStatus(i, true);
  167. }
  168. }
  169. recursive = false;
  170. }
  171. }
  172. void relaySave() {
  173. unsigned char bit = 1;
  174. unsigned char mask = 0;
  175. for (unsigned int i=0; i < _relays.size(); i++) {
  176. if (relayStatus(i)) mask += bit;
  177. bit += bit;
  178. }
  179. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  180. DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
  181. EEPROM.commit();
  182. }
  183. void relayRetrieve(bool invert) {
  184. recursive = true;
  185. unsigned char bit = 1;
  186. unsigned char mask = invert ? ~EEPROM.read(EEPROM_RELAY_STATUS) : EEPROM.read(EEPROM_RELAY_STATUS);
  187. DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
  188. for (unsigned int id=0; id < _relays.size(); id++) {
  189. _relays[id].scheduledStatus = ((mask & bit) == bit);
  190. _relays[id].scheduledReport = true;
  191. bit += bit;
  192. }
  193. if (invert) {
  194. EEPROM.write(EEPROM_RELAY_STATUS, mask);
  195. EEPROM.commit();
  196. }
  197. recursive = false;
  198. }
  199. void relayToggle(unsigned char id) {
  200. if (id >= _relays.size()) return;
  201. relayStatus(id, !relayStatus(id));
  202. }
  203. unsigned char relayCount() {
  204. return _relays.size();
  205. }
  206. //------------------------------------------------------------------------------
  207. // REST API
  208. //------------------------------------------------------------------------------
  209. void relaySetupAPI() {
  210. // API entry points (protected with apikey)
  211. for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
  212. char url[15];
  213. sprintf(url, "%s/%d", MQTT_TOPIC_RELAY, relayID);
  214. char key[10];
  215. sprintf(key, "%s%d", MQTT_TOPIC_RELAY, relayID);
  216. apiRegister(url, key,
  217. [relayID](char * buffer, size_t len) {
  218. snprintf(buffer, len, "%d", relayStatus(relayID) ? 1 : 0);
  219. },
  220. [relayID](const char * payload) {
  221. unsigned int value = payload[0] - '0';
  222. if (value == 2) {
  223. relayToggle(relayID);
  224. } else {
  225. relayStatus(relayID, value == 1);
  226. }
  227. }
  228. );
  229. }
  230. }
  231. //------------------------------------------------------------------------------
  232. // WebSockets
  233. //------------------------------------------------------------------------------
  234. void relayWS() {
  235. String output = relayString();
  236. wsSend(output.c_str());
  237. }
  238. //------------------------------------------------------------------------------
  239. // Domoticz
  240. //------------------------------------------------------------------------------
  241. #if ENABLE_DOMOTICZ
  242. void relayDomoticzSend(unsigned int relayID) {
  243. char buffer[15];
  244. sprintf(buffer, "dczRelayIdx%d", relayID);
  245. domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
  246. }
  247. int relayFromIdx(unsigned int idx) {
  248. for (int relayID=0; relayID<relayCount(); relayID++) {
  249. if (relayToIdx(relayID) == idx) {
  250. return relayID;
  251. }
  252. }
  253. return -1;
  254. }
  255. int relayToIdx(unsigned int relayID) {
  256. char buffer[15];
  257. sprintf(buffer, "dczRelayIdx%d", relayID);
  258. return getSetting(buffer).toInt();
  259. }
  260. void relayDomoticzSetup() {
  261. mqttRegister([](unsigned int type, const char * topic, const char * payload) {
  262. String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
  263. if (type == MQTT_CONNECT_EVENT) {
  264. mqttSubscribeRaw(dczTopicOut.c_str());
  265. }
  266. if (type == MQTT_MESSAGE_EVENT) {
  267. // Check topic
  268. if (dczTopicOut.equals(topic)) {
  269. // Parse response
  270. DynamicJsonBuffer jsonBuffer;
  271. JsonObject& root = jsonBuffer.parseObject((char *) payload);
  272. if (!root.success()) {
  273. DEBUG_MSG_P(PSTR("[DOMOTICZ] Error parsing data\n"));
  274. return;
  275. }
  276. // IDX
  277. unsigned long idx = root["idx"];
  278. int relayID = relayFromIdx(idx);
  279. if (relayID >= 0) {
  280. unsigned long value = root["nvalue"];
  281. DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
  282. relayStatus(relayID, value == 1);
  283. }
  284. }
  285. }
  286. });
  287. }
  288. #endif
  289. //------------------------------------------------------------------------------
  290. // MQTT
  291. //------------------------------------------------------------------------------
  292. void relayMQTT(unsigned char id) {
  293. if (id >= _relays.size()) return;
  294. mqttSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
  295. }
  296. #if ENABLE_INFLUXDB
  297. void relayInfluxDB(unsigned char id) {
  298. if (id >= _relays.size()) return;
  299. char buffer[10];
  300. sprintf(buffer, "%s,id=%d", MQTT_TOPIC_RELAY, id);
  301. influxDBSend(buffer, relayStatus(id) ? "1" : "0");
  302. }
  303. #endif
  304. void relayMQTT() {
  305. for (unsigned int i=0; i < _relays.size(); i++) {
  306. relayMQTT(i);
  307. }
  308. }
  309. void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  310. if (type == MQTT_CONNECT_EVENT) {
  311. #if not MQTT_REPORT_RELAY
  312. relayMQTT();
  313. #endif
  314. char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
  315. sprintf(buffer, "%s/+", MQTT_TOPIC_RELAY);
  316. mqttSubscribe(buffer);
  317. }
  318. if (type == MQTT_MESSAGE_EVENT) {
  319. // Match topic
  320. String t = mqttSubtopic((char *) topic);
  321. if (!t.startsWith(MQTT_TOPIC_RELAY)) return;
  322. // Get value
  323. unsigned int value = (char)payload[0] - '0';
  324. // Pulse topic
  325. if (t.endsWith("pulse")) {
  326. relayPulseMode(value, mqttForward());
  327. return;
  328. }
  329. // Get relay ID
  330. unsigned int relayID = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
  331. if (relayID >= relayCount()) {
  332. DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), relayID);
  333. return;
  334. }
  335. // Action to perform
  336. if (value == 2) {
  337. relayToggle(relayID);
  338. } else {
  339. relayStatus(relayID, value > 0, mqttForward());
  340. }
  341. }
  342. }
  343. void relaySetupMQTT() {
  344. mqttRegister(relayMQTTCallback);
  345. }
  346. //------------------------------------------------------------------------------
  347. // Setup
  348. //------------------------------------------------------------------------------
  349. void relaySetup() {
  350. #if defined(SONOFF_DUAL)
  351. // Two dummy relays for the dual
  352. _relays.push_back((relay_t) {0, 0});
  353. _relays.push_back((relay_t) {0, 0});
  354. #elif defined(AI_LIGHT) | defined(LED_CONTROLLER) | defined(H801_LED_CONTROLLER)
  355. // One dummy relay for the AI Thinker Light & Magic Home and H801 led controllers
  356. _relays.push_back((relay_t) {0, 0});
  357. #else
  358. #ifdef RELAY1_PIN
  359. _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_PIN_INVERSE, RELAY1_LED });
  360. #endif
  361. #ifdef RELAY2_PIN
  362. _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_PIN_INVERSE, RELAY2_LED });
  363. #endif
  364. #ifdef RELAY3_PIN
  365. _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_PIN_INVERSE, RELAY3_LED });
  366. #endif
  367. #ifdef RELAY4_PIN
  368. _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_PIN_INVERSE, RELAY4_LED });
  369. #endif
  370. #endif
  371. byte relayMode = getSetting("relayMode", RELAY_MODE).toInt();
  372. for (unsigned int i=0; i < _relays.size(); i++) {
  373. pinMode(_relays[i].pin, OUTPUT);
  374. if (relayMode == RELAY_MODE_OFF) relayStatus(i, false);
  375. if (relayMode == RELAY_MODE_ON) relayStatus(i, true);
  376. }
  377. if (relayMode == RELAY_MODE_SAME) relayRetrieve(false);
  378. if (relayMode == RELAY_MODE_TOOGLE) relayRetrieve(true);
  379. relayLoop();
  380. relaySetupAPI();
  381. relaySetupMQTT();
  382. #if ENABLE_DOMOTICZ
  383. relayDomoticzSetup();
  384. #endif
  385. DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
  386. }
  387. void relayLoop(void) {
  388. unsigned char id;
  389. for (id = 0; id < _relays.size(); id++) {
  390. unsigned int currentTime = millis();
  391. bool status = _relays[id].scheduledStatus;
  392. if (relayStatus(id) != status && currentTime >= _relays[id].scheduledStatusTime) {
  393. DEBUG_MSG_P(PSTR("[RELAY] %d => %s\n"), id, status ? "ON" : "OFF");
  394. relayProviderStatus(id, status);
  395. if (_relays[id].led > 0) {
  396. ledStatus(_relays[id].led - 1, status);
  397. }
  398. if (_relays[id].scheduledReport) relayMQTT(id);
  399. if (!recursive) {
  400. relayPulse(id);
  401. relaySync(id);
  402. relaySave();
  403. relayWS();
  404. }
  405. #if ENABLE_DOMOTICZ
  406. relayDomoticzSend(id);
  407. #endif
  408. #if ENABLE_INFLUXDB
  409. relayInfluxDB(id);
  410. #endif
  411. _relays[id].scheduledReport = false;
  412. }
  413. }
  414. }