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.

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