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.

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