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.

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