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.

386 lines
10 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 <ArduinoJson.h>
  7. #include <vector>
  8. #include <functional>
  9. typedef struct {
  10. unsigned char pin;
  11. bool reverse;
  12. } relay_t;
  13. std::vector<relay_t> _relays;
  14. #ifdef SONOFF_DUAL
  15. unsigned char dualRelayStatus = 0;
  16. #endif
  17. bool recursive = false;
  18. // -----------------------------------------------------------------------------
  19. // RELAY
  20. // -----------------------------------------------------------------------------
  21. String relayString() {
  22. DynamicJsonBuffer jsonBuffer;
  23. JsonObject& root = jsonBuffer.createObject();
  24. JsonArray& relay = root.createNestedArray("relayStatus");
  25. for (unsigned char i=0; i<relayCount(); i++) {
  26. relay.add(relayStatus(i));
  27. }
  28. String output;
  29. root.printTo(output);
  30. return output;
  31. }
  32. bool relayStatus(unsigned char id) {
  33. #ifdef SONOFF_DUAL
  34. if (id >= 2) return false;
  35. return ((dualRelayStatus & (1 << id)) > 0);
  36. #else
  37. if (id >= _relays.size()) return false;
  38. bool status = (digitalRead(_relays[id].pin) == HIGH);
  39. return _relays[id].reverse ? !status : status;
  40. #endif
  41. }
  42. bool relayStatus(unsigned char id, bool status, bool report) {
  43. if (id >= _relays.size()) return false;
  44. bool changed = false;
  45. if (relayStatus(id) != status) {
  46. DEBUG_MSG("[RELAY] %d => %s\n", id, status ? "ON" : "OFF");
  47. changed = true;
  48. #ifdef SONOFF_DUAL
  49. dualRelayStatus ^= (1 << id);
  50. Serial.flush();
  51. Serial.write(0xA0);
  52. Serial.write(0x04);
  53. Serial.write(dualRelayStatus);
  54. Serial.write(0xA1);
  55. Serial.flush();
  56. #else
  57. digitalWrite(_relays[id].pin, _relays[id].reverse ? !status : status);
  58. #endif
  59. if (report) relayMQTT(id);
  60. if (!recursive) {
  61. relaySync(id);
  62. relaySave();
  63. relayWS();
  64. }
  65. #ifdef ENABLE_DOMOTICZ
  66. relayDomoticzSend(id);
  67. #endif
  68. }
  69. return changed;
  70. }
  71. bool relayStatus(unsigned char id, bool status) {
  72. if (id >= _relays.size()) return false;
  73. return relayStatus(id, status, true);
  74. }
  75. void relaySync(unsigned char id) {
  76. if (_relays.size() > 1) {
  77. recursive = true;
  78. byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt();
  79. bool status = relayStatus(id);
  80. // If RELAY_SYNC_SAME all relays should have the same state
  81. if (relaySync == RELAY_SYNC_SAME) {
  82. for (unsigned short i=0; i<_relays.size(); i++) {
  83. if (i != id) relayStatus(i, status);
  84. }
  85. // If NONE_OR_ONE or ONE and setting ON we should set OFF all the others
  86. } else if (status) {
  87. if (relaySync != RELAY_SYNC_ANY) {
  88. for (unsigned short i=0; i<_relays.size(); i++) {
  89. if (i != id) relayStatus(i, false);
  90. }
  91. }
  92. // If ONLY_ONE and setting OFF we should set ON the other one
  93. } else {
  94. if (relaySync == RELAY_SYNC_ONE) {
  95. unsigned char i = (id + 1) % _relays.size();
  96. relayStatus(i, true);
  97. }
  98. }
  99. recursive = false;
  100. }
  101. }
  102. void relaySave() {
  103. unsigned char bit = 1;
  104. unsigned char mask = 0;
  105. for (unsigned int i=0; i < _relays.size(); i++) {
  106. if (relayStatus(i)) mask += bit;
  107. bit += bit;
  108. }
  109. EEPROM.write(0, mask);
  110. EEPROM.commit();
  111. }
  112. void relayRetrieve() {
  113. recursive = true;
  114. unsigned char bit = 1;
  115. unsigned char mask = EEPROM.read(0);
  116. for (unsigned int i=0; i < _relays.size(); i++) {
  117. relayStatus(i, ((mask & bit) == bit));
  118. bit += bit;
  119. }
  120. recursive = false;
  121. }
  122. void relayToggle(unsigned char id) {
  123. if (id >= _relays.size()) return;
  124. relayStatus(id, !relayStatus(id));
  125. }
  126. unsigned char relayCount() {
  127. return _relays.size();
  128. }
  129. //------------------------------------------------------------------------------
  130. // REST API
  131. //------------------------------------------------------------------------------
  132. void relaySetupAPI() {
  133. // API entry points (protected with apikey)
  134. for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
  135. char url[15];
  136. sprintf(url, "/api/relay/%d", relayID);
  137. char key[10];
  138. sprintf(key, "relay%d", relayID);
  139. apiRegister(url, key,
  140. [relayID]() {
  141. return (char *) (relayStatus(relayID) ? "1" : "0");
  142. },
  143. [relayID](const char * payload) {
  144. unsigned int value = payload[0] - '0';
  145. if (value == 2) {
  146. relayToggle(relayID);
  147. } else {
  148. relayStatus(relayID, value == 1);
  149. }
  150. });
  151. }
  152. }
  153. //------------------------------------------------------------------------------
  154. // WebSockets
  155. //------------------------------------------------------------------------------
  156. void relayWS() {
  157. String output = relayString();
  158. wsSend(output.c_str());
  159. }
  160. //------------------------------------------------------------------------------
  161. // Domoticz
  162. //------------------------------------------------------------------------------
  163. #if ENABLE_DOMOTICZ
  164. void relayDomoticzSend(unsigned int relayID) {
  165. char buffer[15];
  166. sprintf(buffer, "dczRelayIdx%d", relayID);
  167. domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
  168. }
  169. int relayFromIdx(unsigned int idx) {
  170. for (int relayID=0; relayID<relayCount(); relayID++) {
  171. if (relayToIdx(relayID) == idx) {
  172. return relayID;
  173. }
  174. }
  175. return -1;
  176. }
  177. int relayToIdx(unsigned int relayID) {
  178. char buffer[15];
  179. sprintf(buffer, "dczRelayIdx%d", relayID);
  180. return getSetting(buffer).toInt();
  181. }
  182. void relayDomoticzSetup() {
  183. mqttRegister([](unsigned int type, const char * topic, const char * payload) {
  184. String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
  185. if (type == MQTT_CONNECT_EVENT) {
  186. mqttSubscribeRaw(dczTopicOut.c_str());
  187. }
  188. if (type == MQTT_MESSAGE_EVENT) {
  189. // Check topic
  190. if (dczTopicOut.equals(topic)) {
  191. // Parse response
  192. DynamicJsonBuffer jsonBuffer;
  193. JsonObject& root = jsonBuffer.parseObject((char *) payload);
  194. if (!root.success()) {
  195. DEBUG_MSG("[DOMOTICZ] Error parsing data\n");
  196. return;
  197. }
  198. // IDX
  199. unsigned long idx = root["idx"];
  200. int relayID = relayFromIdx(idx);
  201. if (relayID >= 0) {
  202. unsigned long value = root["nvalue"];
  203. DEBUG_MSG("[DOMOTICZ] Received value %d for IDX %d\n", value, idx);
  204. relayStatus(relayID, value == 1);
  205. }
  206. }
  207. }
  208. });
  209. }
  210. #endif
  211. //------------------------------------------------------------------------------
  212. // MQTT
  213. //------------------------------------------------------------------------------
  214. void relayMQTT(unsigned char id) {
  215. if (id >= _relays.size()) return;
  216. String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
  217. char buffer[strlen(MQTT_RELAY_TOPIC) + mqttGetter.length() + 3];
  218. sprintf(buffer, "%s/%d%s", MQTT_RELAY_TOPIC, id, mqttGetter.c_str());
  219. mqttSend(buffer, relayStatus(id) ? "1" : "0");
  220. }
  221. void relayMQTT() {
  222. for (unsigned int i=0; i < _relays.size(); i++) {
  223. relayMQTT(i);
  224. }
  225. }
  226. void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
  227. String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
  228. String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
  229. bool sameSetGet = mqttGetter.compareTo(mqttSetter) == 0;
  230. if (type == MQTT_CONNECT_EVENT) {
  231. relayMQTT();
  232. char buffer[strlen(MQTT_RELAY_TOPIC) + mqttSetter.length() + 3];
  233. sprintf(buffer, "%s/+%s", MQTT_RELAY_TOPIC, mqttSetter.c_str());
  234. mqttSubscribe(buffer);
  235. }
  236. if (type == MQTT_MESSAGE_EVENT) {
  237. // Match topic
  238. String t = String(topic + mqttTopicRootLength());
  239. if (!t.startsWith(MQTT_RELAY_TOPIC)) return;
  240. if (!t.endsWith(mqttSetter)) return;
  241. // Get relay ID
  242. unsigned int relayID = topic[strlen(topic) - mqttSetter.length() - 1] - '0';
  243. if (relayID >= relayCount()) {
  244. DEBUG_MSG("[RELAY] Wrong relayID (%d)\n", relayID);
  245. return;
  246. }
  247. // Action to perform
  248. unsigned int value = (char)payload[0] - '0';
  249. if (value == 2) {
  250. relayToggle(relayID);
  251. } else {
  252. relayStatus(relayID, value > 0, !sameSetGet);
  253. }
  254. }
  255. }
  256. void relaySetupMQTT() {
  257. mqttRegister(relayMQTTCallback);
  258. }
  259. //------------------------------------------------------------------------------
  260. // Setup
  261. //------------------------------------------------------------------------------
  262. void relaySetup() {
  263. #ifdef SONOFF_DUAL
  264. // Two dummy relays for the dual
  265. _relays.push_back((relay_t) {0, 0});
  266. _relays.push_back((relay_t) {0, 0});
  267. #else
  268. #ifdef RELAY1_PIN
  269. _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_PIN_INVERSE });
  270. #endif
  271. #ifdef RELAY2_PIN
  272. _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_PIN_INVERSE });
  273. #endif
  274. #ifdef RELAY3_PIN
  275. _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_PIN_INVERSE });
  276. #endif
  277. #ifdef RELAY4_PIN
  278. _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_PIN_INVERSE });
  279. #endif
  280. #endif
  281. EEPROM.begin(4096);
  282. byte relayMode = getSetting("relayMode", RELAY_MODE).toInt();
  283. for (unsigned int i=0; i < _relays.size(); i++) {
  284. pinMode(_relays[i].pin, OUTPUT);
  285. if (relayMode == RELAY_MODE_OFF) relayStatus(i, false);
  286. if (relayMode == RELAY_MODE_ON) relayStatus(i, true);
  287. }
  288. if (relayMode == RELAY_MODE_SAME) relayRetrieve();
  289. relaySetupAPI();
  290. relaySetupMQTT();
  291. #if ENABLE_DOMOTICZ
  292. relayDomoticzSetup();
  293. #endif
  294. DEBUG_MSG("[RELAY] Number of relays: %d\n", _relays.size());
  295. }