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.

343 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 HLW8012_SUPPORT
  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. #if WEB_SUPPORT
  132. apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
  133. if (_hlwReady) {
  134. snprintf_P(buffer, len, PSTR("%d"), _hlwPower);
  135. } else {
  136. buffer = NULL;
  137. }
  138. });
  139. apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
  140. if (_hlwReady) {
  141. dtostrf(_hlwCurrent, len-1, 3, buffer);
  142. } else {
  143. buffer = NULL;
  144. }
  145. });
  146. apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
  147. if (_hlwReady) {
  148. snprintf_P(buffer, len, PSTR("%d"), _hlwVoltage);
  149. } else {
  150. buffer = NULL;
  151. }
  152. });
  153. #endif // WEB_SUPPORT
  154. }
  155. void hlw8012Loop() {
  156. static unsigned long last_update = 0;
  157. static unsigned char report_count = HLW8012_REPORT_EVERY;
  158. static bool power_spike = false;
  159. static unsigned long power_sum = 0;
  160. static unsigned long power_previous = 0;
  161. static bool current_spike = false;
  162. static double current_sum = 0;
  163. static double current_previous = 0;
  164. static bool voltage_spike = false;
  165. static unsigned long voltage_sum = 0;
  166. static unsigned long voltage_previous = 0;
  167. static bool powWasEnabled = false;
  168. // POW is disabled while there is no internet connection
  169. // When the HLW8012 measurements are enabled back we reset the timer
  170. if (!_hlw8012Enabled) {
  171. powWasEnabled = false;
  172. return;
  173. }
  174. if (!powWasEnabled) {
  175. last_update = millis();
  176. powWasEnabled = true;
  177. }
  178. if (millis() - last_update > HLW8012_UPDATE_INTERVAL) {
  179. last_update = millis();
  180. unsigned int power = getActivePower();
  181. unsigned int voltage = getVoltage();
  182. double current = getCurrent();
  183. if (power > 0) {
  184. power_spike = (power_previous == 0);
  185. } else if (power_spike) {
  186. power_sum -= power_previous;
  187. power_spike = false;
  188. }
  189. power_previous = power;
  190. if (current > 0) {
  191. current_spike = (current_previous == 0);
  192. } else if (current_spike) {
  193. current_sum -= current_previous;
  194. current_spike = false;
  195. }
  196. current_previous = current;
  197. if (voltage > 0) {
  198. voltage_spike = (voltage_previous == 0);
  199. } else if (voltage_spike) {
  200. voltage_sum -= voltage_previous;
  201. voltage_spike = false;
  202. }
  203. voltage_previous = voltage;
  204. #if WEB_SUPPORT
  205. {
  206. unsigned int apparent = getApparentPower();
  207. double factor = getPowerFactor();
  208. unsigned int reactive = getReactivePower();
  209. DynamicJsonBuffer jsonBuffer;
  210. JsonObject& root = jsonBuffer.createObject();
  211. root["powVisible"] = 1;
  212. root["powActivePower"] = power;
  213. root["powCurrent"] = String(current, 3);
  214. root["powVoltage"] = voltage;
  215. root["powApparentPower"] = apparent;
  216. root["powReactivePower"] = reactive;
  217. root["powPowerFactor"] = String(factor, 2);
  218. String output;
  219. root.printTo(output);
  220. wsSend(output.c_str());
  221. }
  222. #endif
  223. if (--report_count == 0) {
  224. // Update globals
  225. _hlwPower = power_sum / HLW8012_REPORT_EVERY;
  226. _hlwCurrent = current_sum / HLW8012_REPORT_EVERY;
  227. _hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY;
  228. _hlwReady = true;
  229. // Calculate subproducts (apparent and reactive power, power factor and delta energy)
  230. unsigned int apparent = _hlwCurrent * _hlwVoltage;
  231. unsigned int reactive = (apparent > _hlwPower) ? sqrt(apparent * apparent - _hlwPower * _hlwPower) : 0;
  232. double factor = (apparent > 0) ? (double) _hlwPower / apparent : 1;
  233. if (factor > 1) factor = 1;
  234. double energy_delta = (double) _hlwPower * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
  235. // Report values to MQTT broker
  236. mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
  237. mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
  238. mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
  239. mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
  240. mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
  241. mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
  242. mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
  243. // Report values to Domoticz
  244. #if DOMOTICZ_SUPPORT
  245. {
  246. char buffer[20];
  247. snprintf_P(buffer, strlen(buffer), PSTR("%d;%s"), _hlwPower, String(energy_delta, 3).c_str());
  248. domoticzSend("dczPowIdx", 0, buffer);
  249. snprintf_P(buffer, strlen(buffer), PSTR("%s"), String(energy_delta, 3).c_str());
  250. domoticzSend("dczEnergyIdx", 0, buffer);
  251. snprintf_P(buffer, strlen(buffer), PSTR("%d"), _hlwVoltage);
  252. domoticzSend("dczVoltIdx", 0, buffer);
  253. snprintf_P(buffer, strlen(buffer), PSTR("%s"), String(_hlwCurrent).c_str());
  254. domoticzSend("dczCurrentIdx", 0, buffer);
  255. }
  256. #endif
  257. #if INFLUXDB_SUPPORT
  258. influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
  259. influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
  260. influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
  261. influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
  262. influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
  263. influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
  264. influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
  265. #endif
  266. // Reset counters
  267. power_sum = current_sum = voltage_sum = 0;
  268. report_count = HLW8012_REPORT_EVERY;
  269. }
  270. // Post - Accumulators
  271. power_sum += power_previous;
  272. current_sum += current_previous;
  273. voltage_sum += voltage_previous;
  274. // Toggle between current and voltage monitoring
  275. #if HLW8012_USE_INTERRUPTS == 0
  276. hlw8012.toggleMode();
  277. #endif
  278. }
  279. }
  280. #endif