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.

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