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.

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