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.

334 lines
10 KiB

  1. /*
  2. POWER MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if POWER_PROVIDER != POWER_PROVIDER_NONE
  6. // -----------------------------------------------------------------------------
  7. // MODULE GLOBALS AND CACHE
  8. // -----------------------------------------------------------------------------
  9. #include "power.h"
  10. #include <Hash.h>
  11. #include <ArduinoJson.h>
  12. bool _power_enabled = false;
  13. bool _power_ready = false;
  14. bool _power_newdata = false;
  15. double _power_current = 0;
  16. double _power_voltage = 0;
  17. double _power_apparent = 0;
  18. MedianFilter _filter_current = MedianFilter(POWER_REPORT_BUFFER);
  19. #if POWER_HAS_ACTIVE
  20. double _power_active = 0;
  21. double _power_reactive = 0;
  22. double _power_factor = 0;
  23. MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_BUFFER);
  24. MedianFilter _filter_active = MedianFilter(POWER_REPORT_BUFFER);
  25. MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_BUFFER);
  26. #endif
  27. // -----------------------------------------------------------------------------
  28. // PRIVATE METHODS
  29. // -----------------------------------------------------------------------------
  30. #if WEB_SUPPORT
  31. void _powerAPISetup() {
  32. apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) {
  33. if (_power_ready) {
  34. dtostrf(getCurrent(), len-1, POWER_CURRENT_PRECISION, buffer);
  35. } else {
  36. buffer = NULL;
  37. }
  38. });
  39. apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) {
  40. if (_power_ready) {
  41. snprintf_P(buffer, len, PSTR("%d"), getVoltage());
  42. } else {
  43. buffer = NULL;
  44. }
  45. });
  46. apiRegister(MQTT_TOPIC_POWER_APPARENT, MQTT_TOPIC_POWER_APPARENT, [](char * buffer, size_t len) {
  47. if (_power_ready) {
  48. snprintf_P(buffer, len, PSTR("%d"), getApparentPower());
  49. } else {
  50. buffer = NULL;
  51. }
  52. });
  53. #if POWER_HAS_ACTIVE
  54. apiRegister(MQTT_TOPIC_POWER_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](char * buffer, size_t len) {
  55. if (_power_ready) {
  56. snprintf_P(buffer, len, PSTR("%d"), getActivePower());
  57. } else {
  58. buffer = NULL;
  59. }
  60. });
  61. #endif
  62. }
  63. #endif // WEB_SUPPORT
  64. void _powerReset() {
  65. _filter_current.reset();
  66. #if POWER_HAS_ACTIVE
  67. _filter_apparent.reset();
  68. _filter_voltage.reset();
  69. _filter_active.reset();
  70. #endif
  71. }
  72. void _powerRead() {
  73. // Get instantaneous values from HAL
  74. double current = _powerCurrent();
  75. double voltage = _powerVoltage();
  76. double apparent = _powerApparentPower();
  77. #if POWER_HAS_ACTIVE
  78. double active = _powerActivePower();
  79. double reactive = (apparent > active) ? sqrt(apparent * apparent - active * active) : 0;
  80. double factor = (apparent > 0) ? active / apparent : 1;
  81. if (factor > 1) factor = 1;
  82. #endif
  83. // Filters
  84. _filter_current.add(current);
  85. #if POWER_HAS_ACTIVE
  86. _filter_apparent.add(apparent);
  87. _filter_voltage.add(voltage);
  88. _filter_active.add(active);
  89. #endif
  90. /* THERE IS A BUG HERE SOMEWHERE :)
  91. char current_buffer[10];
  92. dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_PRECISION, current_buffer);
  93. DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer);
  94. DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), int(voltage));
  95. DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), int(apparent));
  96. #if POWER_HAS_ACTIVE
  97. DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), int(active));
  98. DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), int(reactive));
  99. DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), int(100 * factor));
  100. #endif
  101. */
  102. // Update websocket clients
  103. #if WEB_SUPPORT
  104. if (wsConnected()) {
  105. DynamicJsonBuffer jsonBuffer;
  106. JsonObject& root = jsonBuffer.createObject();
  107. root["pwrVisible"] = 1;
  108. root["pwrCurrent"] = roundTo(current, POWER_CURRENT_DECIMALS);
  109. root["pwrVoltage"] = roundTo(voltage, POWER_VOLTAGE_DECIMALS);
  110. root["pwrApparent"] = roundTo(apparent, POWER_POWER_DECIMALS);
  111. #if POWER_HAS_ACTIVE
  112. root["pwrActive"] = roundTo(active, POWER_POWER_DECIMALS);
  113. root["pwrReactive"] = roundTo(reactive, POWER_POWER_DECIMALS);
  114. root["pwrFactor"] = int(100 * factor);
  115. #endif
  116. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  117. root["emonVisible"] = 1;
  118. #endif
  119. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  120. root["hlwVisible"] = 1;
  121. #endif
  122. #if POWER_PROVIDER == POWER_PROVIDER_V9261F
  123. root["v9261fVisible"] = 1;
  124. #endif
  125. #if POWER_PROVIDER == POWER_PROVIDER_ECH1560
  126. root["ech1560Visible"] = 1;
  127. #endif
  128. String output;
  129. root.printTo(output);
  130. wsSend(output.c_str());
  131. }
  132. #endif
  133. }
  134. void _powerReport() {
  135. // Get the fitered values
  136. _power_current = _filter_current.average(true);
  137. #if POWER_HAS_ACTIVE
  138. _power_apparent = _filter_apparent.average(true);
  139. _power_voltage = _filter_voltage.average(true);
  140. _power_active = _filter_active.average(true);
  141. if (_power_active > _power_apparent) _power_apparent = _power_active;
  142. _power_reactive = (_power_apparent > _power_active) ? sqrt(_power_apparent * _power_apparent - _power_active * _power_active) : 0;
  143. _power_factor = (_power_apparent > 0) ? _power_active / _power_apparent : 1;
  144. if (_power_factor > 1) _power_factor = 1;
  145. double power = _power_active;
  146. #else
  147. _power_apparent = _power_current * _power_voltage;
  148. double power = _power_apparent;
  149. #endif
  150. double energy_delta = power * POWER_ENERGY_FACTOR;
  151. _power_ready = true;
  152. char buf_current[10];
  153. char buf_energy[10];
  154. dtostrf(_power_current, -9, POWER_CURRENT_PRECISION, buf_current);
  155. dtostrf(energy_delta, -9, POWER_CURRENT_PRECISION, buf_energy);
  156. {
  157. mqttSend(MQTT_TOPIC_CURRENT, buf_current);
  158. mqttSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
  159. mqttSend(MQTT_TOPIC_ENERGY, buf_energy);
  160. #if POWER_HAS_ACTIVE
  161. mqttSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
  162. mqttSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
  163. mqttSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
  164. mqttSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
  165. #endif
  166. }
  167. #if DOMOTICZ_SUPPORT
  168. if (domoticzEnabled()) {
  169. char buffer[20];
  170. snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, buf_energy);
  171. domoticzSend("dczPowIdx", 0, buffer);
  172. domoticzSend("dczCurrentIdx", 0, buf_current);
  173. domoticzSend("dczEnergyIdx", 0, buf_energy);
  174. #if POWER_HAS_ACTIVE
  175. snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _power_voltage);
  176. domoticzSend("dczVoltIdx", 0, buffer);
  177. #endif
  178. }
  179. #endif
  180. #if INFLUXDB_SUPPORT
  181. if (influxdbEnabled()) {
  182. influxDBSend(MQTT_TOPIC_CURRENT, buf_current);
  183. influxDBSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
  184. influxDBSend(MQTT_TOPIC_ENERGY, buf_energy);
  185. #if POWER_HAS_ACTIVE
  186. influxDBSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
  187. influxDBSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
  188. influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
  189. influxDBSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
  190. #endif
  191. }
  192. #endif
  193. }
  194. // -----------------------------------------------------------------------------
  195. // MAGNITUDE API
  196. // -----------------------------------------------------------------------------
  197. bool hasActivePower() {
  198. return POWER_HAS_ACTIVE;
  199. }
  200. double getCurrent() {
  201. return roundTo(_power_current, POWER_CURRENT_DECIMALS);
  202. }
  203. double getVoltage() {
  204. return roundTo(_power_voltage, POWER_VOLTAGE_DECIMALS);
  205. }
  206. double getApparentPower() {
  207. return roundTo(_power_apparent, POWER_POWER_DECIMALS);
  208. }
  209. #if POWER_HAS_ACTIVE
  210. double getActivePower() {
  211. return roundTo(_power_active, POWER_POWER_DECIMALS);
  212. }
  213. double getReactivePower() {
  214. return roundTo(_power_reactive, POWER_POWER_DECIMALS);
  215. }
  216. double getPowerFactor() {
  217. return roundTo(_power_factor, 2);
  218. }
  219. #endif
  220. // -----------------------------------------------------------------------------
  221. // PUBLIC API
  222. // -----------------------------------------------------------------------------
  223. bool powerEnabled() {
  224. return _power_enabled;
  225. }
  226. void powerEnabled(bool enabled) {
  227. if (enabled & !_power_enabled) _powerReset();
  228. _power_enabled = enabled;
  229. _powerEnabledProvider();
  230. }
  231. void powerCalibrate(unsigned char magnitude, double value) {
  232. _powerCalibrateProvider(magnitude, value);
  233. }
  234. void powerResetCalibration() {
  235. _powerResetCalibrationProvider();
  236. }
  237. void powerConfigure() {
  238. _powerConfigureProvider();
  239. }
  240. void powerSetup() {
  241. // backwards compatibility
  242. moveSetting("pwMainsVoltage", "pwrVoltage");
  243. moveSetting("emonMains", "pwrVoltage");
  244. moveSetting("emonVoltage", "pwrVoltage");
  245. moveSetting("pwCurrentRatio", "pwrRatioC");
  246. moveSetting("emonRatio", "pwrRatioC");
  247. moveSetting("powPowerMult", "pwrRatioP");
  248. moveSetting("powCurrentMult", "pwrRatioC");
  249. moveSetting("powVoltageMult", "pwrRatioV");
  250. moveSetting("powerVoltage", "pwrVoltage");
  251. moveSetting("powerRatioC", "pwrRatioC");
  252. moveSetting("powerRatioV", "pwrRatioV");
  253. moveSetting("powerRatioP", "pwrRatioP");
  254. _powerSetupProvider();
  255. // API
  256. #if WEB_SUPPORT
  257. _powerAPISetup();
  258. #endif
  259. DEBUG_MSG_P(PSTR("[POWER] POWER_PROVIDER = %d\n"), POWER_PROVIDER);
  260. }
  261. void powerLoop() {
  262. _powerLoopProvider(true);
  263. if (_power_newdata) {
  264. _power_newdata = false;
  265. _powerRead();
  266. }
  267. static unsigned long last = 0;
  268. if (millis() - last > POWER_REPORT_INTERVAL) {
  269. last = millis();
  270. _powerReport();
  271. }
  272. _powerLoopProvider(false);
  273. }
  274. #endif // POWER_PROVIDER != POWER_PROVIDER_NONE