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.

339 lines
12 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_HLW8012
  7. #include <HLW8012.h>
  8. #include <Hash.h>
  9. #include <ArduinoJson.h>
  10. HLW8012 hlw8012;
  11. bool _hlw8012Enabled = false;
  12. bool _hlwReady = false;
  13. int _hlwPower = 0;
  14. double _hlwCurrent = 0;
  15. int _hlwVoltage = 0;
  16. // -----------------------------------------------------------------------------
  17. // POW
  18. // -----------------------------------------------------------------------------
  19. // When using interrupts we have to call the library entry point
  20. // whenever an interrupt is triggered
  21. void ICACHE_RAM_ATTR hlw8012_cf1_interrupt() {
  22. hlw8012.cf1_interrupt();
  23. }
  24. void ICACHE_RAM_ATTR hlw8012_cf_interrupt() {
  25. hlw8012.cf_interrupt();
  26. }
  27. void hlw8012Enable(bool status) {
  28. _hlw8012Enabled = status;
  29. if (_hlw8012Enabled) {
  30. #if HLW8012_USE_INTERRUPTS == 1
  31. attachInterrupt(HLW8012_CF1_PIN, hlw8012_cf1_interrupt, CHANGE);
  32. attachInterrupt(HLW8012_CF_PIN, hlw8012_cf_interrupt, CHANGE);
  33. #endif
  34. DEBUG_MSG_P(PSTR("[POW] Enabled\n"));
  35. } else {
  36. #if HLW8012_USE_INTERRUPTS == 1
  37. detachInterrupt(HLW8012_CF1_PIN);
  38. detachInterrupt(HLW8012_CF_PIN);
  39. #endif
  40. DEBUG_MSG_P(PSTR("[POW] Disabled\n"));
  41. }
  42. }
  43. // -----------------------------------------------------------------------------
  44. void hlw8012SaveCalibration() {
  45. setSetting("powPowerMult", hlw8012.getPowerMultiplier());
  46. setSetting("powCurrentMult", hlw8012.getCurrentMultiplier());
  47. setSetting("powVoltageMult", hlw8012.getVoltageMultiplier());
  48. }
  49. void hlw8012RetrieveCalibration() {
  50. double value;
  51. value = getSetting("powPowerMult", 0).toFloat();
  52. if (value > 0) hlw8012.setPowerMultiplier((int) value);
  53. value = getSetting("powCurrentMult", 0).toFloat();
  54. if (value > 0) hlw8012.setCurrentMultiplier((int) value);
  55. value = getSetting("powVoltageMult", 0).toFloat();
  56. if (value > 0) hlw8012.setVoltageMultiplier((int) value);
  57. }
  58. void hlw8012SetExpectedActivePower(unsigned int power) {
  59. if (power > 0) {
  60. hlw8012.expectedActivePower(power);
  61. hlw8012SaveCalibration();
  62. }
  63. }
  64. void hlw8012SetExpectedCurrent(double current) {
  65. if (current > 0) {
  66. hlw8012.expectedCurrent(current);
  67. hlw8012SaveCalibration();
  68. }
  69. }
  70. void hlw8012SetExpectedVoltage(unsigned int voltage) {
  71. if (voltage > 0) {
  72. hlw8012.expectedVoltage(voltage);
  73. hlw8012SaveCalibration();
  74. }
  75. }
  76. void hlw8012Reset() {
  77. hlw8012.resetMultipliers();
  78. hlw8012SaveCalibration();
  79. }
  80. // -----------------------------------------------------------------------------
  81. // HAL
  82. // -----------------------------------------------------------------------------
  83. unsigned int getActivePower() {
  84. unsigned int power = hlw8012.getActivePower();
  85. if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
  86. return power;
  87. }
  88. unsigned int getApparentPower() {
  89. unsigned int power = hlw8012.getApparentPower();
  90. if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
  91. return power;
  92. }
  93. unsigned int getReactivePower() {
  94. unsigned int power = hlw8012.getReactivePower();
  95. if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
  96. return power;
  97. }
  98. double getCurrent() {
  99. double current = hlw8012.getCurrent();
  100. if (HLW8012_MIN_CURRENT > current || current > HLW8012_MAX_CURRENT) current = 0;
  101. return current;
  102. }
  103. unsigned int getVoltage() {
  104. return hlw8012.getVoltage();
  105. }
  106. double getPowerFactor() {
  107. return hlw8012.getPowerFactor();
  108. }
  109. // -----------------------------------------------------------------------------
  110. void hlw8012Setup() {
  111. // Initialize HLW8012
  112. // 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);
  113. // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
  114. // * currentWhen is the value in sel_pin to select current sampling
  115. // * set use_interrupts to true to use interrupts to monitor pulse widths
  116. // * leave pulse_timeout to the default value, recommended when using interrupts
  117. #if HLW8012_USE_INTERRUPTS
  118. hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true);
  119. #else
  120. hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000);
  121. #endif
  122. // These values are used to calculate current, voltage and power factors as per datasheet formula
  123. // These are the nominal values for the Sonoff POW resistors:
  124. // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
  125. // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
  126. // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
  127. hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
  128. // Retrieve calibration values
  129. hlw8012RetrieveCalibration();
  130. // API definitions
  131. apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
  132. if (_hlwReady) {
  133. snprintf_P(buffer, len, PSTR("%d"), _hlwPower);
  134. } else {
  135. buffer = NULL;
  136. }
  137. });
  138. apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
  139. if (_hlwReady) {
  140. dtostrf(_hlwCurrent, len-1, 3, buffer);
  141. } else {
  142. buffer = NULL;
  143. }
  144. });
  145. apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
  146. if (_hlwReady) {
  147. snprintf_P(buffer, len, PSTR("%d"), _hlwVoltage);
  148. } else {
  149. buffer = NULL;
  150. }
  151. });
  152. }
  153. void hlw8012Loop() {
  154. static unsigned long last_update = 0;
  155. static unsigned char report_count = HLW8012_REPORT_EVERY;
  156. static bool power_spike = false;
  157. static unsigned long power_sum = 0;
  158. static unsigned long power_previous = 0;
  159. static bool current_spike = false;
  160. static double current_sum = 0;
  161. static double current_previous = 0;
  162. static bool voltage_spike = false;
  163. static unsigned long voltage_sum = 0;
  164. static unsigned long voltage_previous = 0;
  165. static bool powWasEnabled = false;
  166. // POW is disabled while there is no internet connection
  167. // When the HLW8012 measurements are enabled back we reset the timer
  168. if (!_hlw8012Enabled) {
  169. powWasEnabled = false;
  170. return;
  171. }
  172. if (!powWasEnabled) {
  173. last_update = millis();
  174. powWasEnabled = true;
  175. }
  176. if (millis() - last_update > HLW8012_UPDATE_INTERVAL) {
  177. last_update = millis();
  178. unsigned int power = getActivePower();
  179. unsigned int voltage = getVoltage();
  180. double current = getCurrent();
  181. if (power > 0) {
  182. power_spike = (power_previous == 0);
  183. } else if (power_spike) {
  184. power_sum -= power_previous;
  185. power_spike = false;
  186. }
  187. power_previous = power;
  188. if (current > 0) {
  189. current_spike = (current_previous == 0);
  190. } else if (current_spike) {
  191. current_sum -= current_previous;
  192. current_spike = false;
  193. }
  194. current_previous = current;
  195. if (voltage > 0) {
  196. voltage_spike = (voltage_previous == 0);
  197. } else if (voltage_spike) {
  198. voltage_sum -= voltage_previous;
  199. voltage_spike = false;
  200. }
  201. voltage_previous = voltage;
  202. if (wsConnected()) {
  203. unsigned int apparent = getApparentPower();
  204. double factor = getPowerFactor();
  205. unsigned int reactive = getReactivePower();
  206. DynamicJsonBuffer jsonBuffer;
  207. JsonObject& root = jsonBuffer.createObject();
  208. root["powVisible"] = 1;
  209. root["powActivePower"] = power;
  210. root["powCurrent"] = String(current, 3);
  211. root["powVoltage"] = voltage;
  212. root["powApparentPower"] = apparent;
  213. root["powReactivePower"] = reactive;
  214. root["powPowerFactor"] = String(factor, 2);
  215. String output;
  216. root.printTo(output);
  217. wsSend(output.c_str());
  218. }
  219. if (--report_count == 0) {
  220. // Update globals
  221. _hlwPower = power_sum / HLW8012_REPORT_EVERY;
  222. _hlwCurrent = current_sum / HLW8012_REPORT_EVERY;
  223. _hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY;
  224. _hlwReady = true;
  225. // Calculate subproducts (apparent and reactive power, power factor and delta energy)
  226. unsigned int apparent = _hlwCurrent * _hlwVoltage;
  227. unsigned int reactive = (apparent > _hlwPower) ? sqrt(apparent * apparent - _hlwPower * _hlwPower) : 0;
  228. double factor = (apparent > 0) ? (double) _hlwPower / apparent : 1;
  229. if (factor > 1) factor = 1;
  230. double energy_delta = (double) _hlwPower * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
  231. // Report values to MQTT broker
  232. mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
  233. mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
  234. mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
  235. mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
  236. mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
  237. mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
  238. mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
  239. // Report values to Domoticz
  240. #if ENABLE_DOMOTICZ
  241. {
  242. char buffer[20];
  243. snprintf_P(buffer, strlen(buffer), PSTR("%d;%s"), _hlwPower, String(energy_delta, 3).c_str());
  244. domoticzSend("dczPowIdx", 0, buffer);
  245. snprintf_P(buffer, strlen(buffer), PSTR("%s"), String(energy_delta, 3).c_str());
  246. domoticzSend("dczEnergyIdx", 0, buffer);
  247. snprintf_P(buffer, strlen(buffer), PSTR("%d"), _hlwVoltage);
  248. domoticzSend("dczVoltIdx", 0, buffer);
  249. snprintf_P(buffer, strlen(buffer), PSTR("%s"), String(_hlwCurrent).c_str());
  250. domoticzSend("dczCurrentIdx", 0, buffer);
  251. }
  252. #endif
  253. #if ENABLE_INFLUXDB
  254. influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
  255. influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
  256. influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
  257. influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
  258. influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
  259. influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
  260. influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
  261. #endif
  262. // Reset counters
  263. power_sum = current_sum = voltage_sum = 0;
  264. report_count = HLW8012_REPORT_EVERY;
  265. }
  266. // Post - Accumulators
  267. power_sum += power_previous;
  268. current_sum += current_previous;
  269. voltage_sum += voltage_previous;
  270. // Toggle between current and voltage monitoring
  271. #if HLW8012_USE_INTERRUPTS == 0
  272. hlw8012.toggleMode();
  273. #endif
  274. }
  275. }
  276. #endif