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.

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