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.

281 lines
8.8 KiB

  1. /*
  2. POW MODULE
  3. Support for Sonoff POW HLW8012-based power monitor
  4. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  5. */
  6. #if ENABLE_POW
  7. #include <HLW8012.h>
  8. #include <Hash.h>
  9. #include <ArduinoJson.h>
  10. #include <EEPROM.h>
  11. HLW8012 hlw8012;
  12. bool _powEnabled = false;
  13. double _energy = 0;
  14. // -----------------------------------------------------------------------------
  15. // POW
  16. // -----------------------------------------------------------------------------
  17. // When using interrupts we have to call the library entry point
  18. // whenever an interrupt is triggered
  19. void hlw8012_cf1_interrupt() {
  20. hlw8012.cf1_interrupt();
  21. }
  22. void hlw8012_cf_interrupt() {
  23. hlw8012.cf_interrupt();
  24. }
  25. void powEnable(bool status) {
  26. _powEnabled = status;
  27. if (_powEnabled) {
  28. #if POW_USE_INTERRUPTS == 1
  29. attachInterrupt(POW_CF1_PIN, hlw8012_cf1_interrupt, CHANGE);
  30. attachInterrupt(POW_CF_PIN, hlw8012_cf_interrupt, CHANGE);
  31. #endif
  32. DEBUG_MSG("[POW] Enabled\n");
  33. } else {
  34. #if POW_USE_INTERRUPTS == 1
  35. detachInterrupt(POW_CF1_PIN);
  36. detachInterrupt(POW_CF_PIN);
  37. #endif
  38. DEBUG_MSG("[POW] Disabled\n");
  39. }
  40. }
  41. // -----------------------------------------------------------------------------
  42. void powSaveCalibration() {
  43. setSetting("powPowerMult", hlw8012.getPowerMultiplier());
  44. setSetting("powCurrentMult", hlw8012.getCurrentMultiplier());
  45. setSetting("powVoltageMult", hlw8012.getVoltageMultiplier());
  46. }
  47. void powRetrieveCalibration() {
  48. double value;
  49. value = getSetting("powPowerMult", 0).toFloat();
  50. if (value > 0) hlw8012.setPowerMultiplier((int) value);
  51. value = getSetting("powCurrentMult", 0).toFloat();
  52. if (value > 0) hlw8012.setCurrentMultiplier((int) value);
  53. value = getSetting("powVoltageMult", 0).toFloat();
  54. if (value > 0) hlw8012.setVoltageMultiplier((int) value);
  55. }
  56. void powSetExpectedActivePower(unsigned int power) {
  57. if (power > 0) {
  58. hlw8012.expectedActivePower(power);
  59. powSaveCalibration();
  60. }
  61. }
  62. void powSetExpectedCurrent(double current) {
  63. if (current > 0) {
  64. hlw8012.expectedCurrent(current);
  65. powSaveCalibration();
  66. }
  67. }
  68. void powSetExpectedVoltage(unsigned int voltage) {
  69. if (voltage > 0) {
  70. hlw8012.expectedVoltage(voltage);
  71. powSaveCalibration();
  72. }
  73. }
  74. void powReset() {
  75. hlw8012.resetMultipliers();
  76. powSaveCalibration();
  77. }
  78. // -----------------------------------------------------------------------------
  79. unsigned int getActivePower() {
  80. return hlw8012.getActivePower();
  81. }
  82. unsigned int getApparentPower() {
  83. return hlw8012.getApparentPower();
  84. }
  85. unsigned int getReactivePower() {
  86. return hlw8012.getReactivePower();
  87. }
  88. double getCurrent() {
  89. return hlw8012.getCurrent();
  90. }
  91. unsigned int getVoltage() {
  92. return hlw8012.getVoltage();
  93. }
  94. unsigned int getPowerFactor() {
  95. return (int) (100 * hlw8012.getPowerFactor());
  96. }
  97. double getEnergy() {
  98. return _energy;
  99. }
  100. // -----------------------------------------------------------------------------
  101. void retrieveEnergy() {
  102. unsigned long energy = EEPROM.read(EEPROM_POWER_COUNT + 1);
  103. energy = (energy << 8) + EEPROM.read(EEPROM_POWER_COUNT);
  104. if (energy == 0xFFFF) energy = 0;
  105. _energy = energy;
  106. }
  107. void saveEnergy() {
  108. unsigned int energy = (int) _energy;
  109. EEPROM.write(EEPROM_POWER_COUNT, energy & 0xFF);
  110. EEPROM.write(EEPROM_POWER_COUNT + 1, (energy >> 8) & 0xFF);
  111. EEPROM.commit();
  112. }
  113. void powSetup() {
  114. // Initialize HLW8012
  115. // void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
  116. // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
  117. // * currentWhen is the value in sel_pin to select current sampling
  118. // * set use_interrupts to true to use interrupts to monitor pulse widths
  119. // * leave pulse_timeout to the default value, recommended when using interrupts
  120. #if POW_USE_INTERRUPTS
  121. hlw8012.begin(POW_CF_PIN, POW_CF1_PIN, POW_SEL_PIN, POW_SEL_CURRENT, true);
  122. #else
  123. hlw8012.begin(POW_CF_PIN, POW_CF1_PIN, POW_SEL_PIN, POW_SEL_CURRENT, false, 1000000);
  124. #endif
  125. // These values are used to calculate current, voltage and power factors as per datasheet formula
  126. // These are the nominal values for the Sonoff POW resistors:
  127. // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
  128. // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
  129. // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
  130. hlw8012.setResistors(POW_CURRENT_R, POW_VOLTAGE_R_UP, POW_VOLTAGE_R_DOWN);
  131. // Retrieve calibration values
  132. powRetrieveCalibration();
  133. // Recover energy reading
  134. retrieveEnergy();
  135. // API definitions
  136. apiRegister("/api/power", "power", [](char * buffer, size_t len) {
  137. snprintf(buffer, len, "%d", getActivePower());
  138. });
  139. apiRegister("/api/energy", "energy", [](char * buffer, size_t len) {
  140. snprintf(buffer, len, "%ld", (unsigned long) _energy);
  141. });
  142. apiRegister("/api/current", "current", [](char * buffer, size_t len) {
  143. dtostrf(getCurrent(), len-1, 2, buffer);
  144. });
  145. apiRegister("/api/voltage", "voltage", [](char * buffer, size_t len) {
  146. snprintf(buffer, len, "%d", getVoltage());
  147. });
  148. }
  149. void powLoop() {
  150. static unsigned long last_update = 0;
  151. static unsigned char report_count = POW_REPORT_EVERY;
  152. static unsigned long power_sum = 0;
  153. static double current_sum = 0;
  154. static unsigned long voltage_sum = 0;
  155. static bool powWasEnabled = false;
  156. // POW is disabled while there is no internet connection
  157. // When the HLW8012 measurements are enabled back we reset the timer
  158. if (!_powEnabled) {
  159. powWasEnabled = false;
  160. return;
  161. }
  162. if (!powWasEnabled) {
  163. last_update = millis();
  164. powWasEnabled = true;
  165. }
  166. if (millis() - last_update > POW_UPDATE_INTERVAL) {
  167. last_update = millis();
  168. unsigned int power = getActivePower();
  169. unsigned int voltage = getVoltage();
  170. double current = getCurrent();
  171. unsigned int apparent = getApparentPower();
  172. unsigned int factor = getPowerFactor();
  173. unsigned int reactive = getReactivePower();
  174. power_sum += power;
  175. current_sum += current;
  176. voltage_sum += voltage;
  177. DynamicJsonBuffer jsonBuffer;
  178. JsonObject& root = jsonBuffer.createObject();
  179. root["powVisible"] = 1;
  180. root["powActivePower"] = power;
  181. root["powCurrent"] = current;
  182. root["powVoltage"] = voltage;
  183. root["powApparentPower"] = apparent;
  184. root["powReactivePower"] = reactive;
  185. root["powPowerFactor"] = factor;
  186. String output;
  187. root.printTo(output);
  188. wsSend(output.c_str());
  189. if (--report_count == 0) {
  190. power = power_sum / POW_REPORT_EVERY;
  191. current = current_sum / POW_REPORT_EVERY;
  192. voltage = voltage_sum / POW_REPORT_EVERY;
  193. apparent = current * voltage;
  194. reactive = (apparent > power) ? sqrt(apparent * apparent - power * power) : 0;
  195. factor = (apparent > 0) ? 100 * power / apparent : 100;
  196. if (factor > 100) factor = 100;
  197. double window = (double) POW_REPORT_EVERY * POW_UPDATE_INTERVAL / 1000.0 / 3600.0;
  198. _energy += power * window;
  199. saveEnergy();
  200. mqttSend(getSetting("powPowerTopic", POW_POWER_TOPIC).c_str(), String(power).c_str());
  201. mqttSend(getSetting("powEnergyTopic", POW_ENERGY_TOPIC).c_str(), String(_energy).c_str());
  202. mqttSend(getSetting("powCurrentTopic", POW_CURRENT_TOPIC).c_str(), String(current).c_str());
  203. mqttSend(getSetting("powVoltageTopic", POW_VOLTAGE_TOPIC).c_str(), String(voltage).c_str());
  204. mqttSend(getSetting("powAPowerTopic", POW_APOWER_TOPIC).c_str(), String(apparent).c_str());
  205. mqttSend(getSetting("powRPowerTopic", POW_RPOWER_TOPIC).c_str(), String(reactive).c_str());
  206. mqttSend(getSetting("powPFactorTopic", POW_PFACTOR_TOPIC).c_str(), String(factor).c_str());
  207. #if ENABLE_DOMOTICZ
  208. {
  209. char buffer[20];
  210. snprintf(buffer, 20, "%d;%ld", power, (unsigned long) _energy);
  211. domoticzSend("dczPowIdx", 0, buffer);
  212. snprintf(buffer, 20, "%d", voltage);
  213. domoticzSend("dczVoltIdx", 0, buffer);
  214. }
  215. #endif
  216. power_sum = current_sum = voltage_sum = 0;
  217. report_count = POW_REPORT_EVERY;
  218. }
  219. // Toggle between current and voltage monitoring
  220. #if POW_USE_INTERRUPTS == 0
  221. hlw8012.toggleMode();
  222. #endif
  223. }
  224. }
  225. #endif