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.

526 lines
16 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. double _power_current = 0;
  15. double _power_voltage = 0;
  16. double _power_apparent = 0;
  17. MedianFilter _filter_current = MedianFilter(POWER_REPORT_EVERY);
  18. #if POWER_HAS_ACTIVE
  19. double _power_active = 0;
  20. MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_EVERY);
  21. MedianFilter _filter_active = MedianFilter(POWER_REPORT_EVERY);
  22. MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_EVERY);
  23. #endif
  24. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  25. #include <EmonLiteESP.h>
  26. EmonLiteESP _emon;
  27. #endif
  28. #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
  29. #include "brzo_i2c.h"
  30. // ADC121 Registers
  31. #define ADC121_REG_RESULT 0x00
  32. #define ADC121_REG_ALERT 0x01
  33. #define ADC121_REG_CONFIG 0x02
  34. #define ADC121_REG_LIMITL 0x03
  35. #define ADC121_REG_LIMITH 0x04
  36. #define ADC121_REG_HYST 0x05
  37. #define ADC121_REG_CONVL 0x06
  38. #define ADC121_REG_CONVH 0x07
  39. #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
  40. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  41. #include <HLW8012.h>
  42. #include <ESP8266WiFi.h>
  43. HLW8012 _hlw8012;
  44. WiFiEventHandler _power_wifi_onconnect;
  45. WiFiEventHandler _power_wifi_ondisconnect;
  46. #endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012
  47. // -----------------------------------------------------------------------------
  48. // PROVIDERS
  49. // -----------------------------------------------------------------------------
  50. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  51. unsigned int currentCallback() {
  52. #if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG
  53. return analogRead(0);
  54. #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG
  55. #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
  56. uint8_t buffer[2];
  57. brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY);
  58. buffer[0] = ADC121_REG_RESULT;
  59. brzo_i2c_write(buffer, 1, false);
  60. brzo_i2c_read(buffer, 2, false);
  61. brzo_i2c_end_transaction();
  62. unsigned int value;
  63. value = (buffer[0] & 0x0F) << 8;
  64. value |= buffer[1];
  65. return value;
  66. #endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
  67. }
  68. #endif // POWER_PROVIDER & POWER_PROVIDER_EMON
  69. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  70. void ICACHE_RAM_ATTR _hlw_cf1_isr() {
  71. _hlw8012.cf1_interrupt();
  72. }
  73. void ICACHE_RAM_ATTR _hlw_cf_isr() {
  74. _hlw8012.cf_interrupt();
  75. }
  76. void _hlwSetCalibration() {
  77. double value;
  78. value = getSetting("powerRatioP", 0).toFloat();
  79. if (value > 0) _hlw8012.setPowerMultiplier(value);
  80. value = getSetting("powerRatioC", 0).toFloat();
  81. if (value > 0) _hlw8012.setCurrentMultiplier(value);
  82. value = getSetting("powerRatioV", 0).toFloat();
  83. if (value > 0) _hlw8012.setVoltageMultiplier(value);
  84. }
  85. void _hlwGetCalibration() {
  86. setSetting("powerRatioP", _hlw8012.getPowerMultiplier());
  87. setSetting("powerRatioC", _hlw8012.getCurrentMultiplier());
  88. setSetting("powerRatioV", _hlw8012.getVoltageMultiplier());
  89. saveSettings();
  90. }
  91. void _hlwResetCalibration() {
  92. _hlw8012.resetMultipliers();
  93. _hlwGetCalibration();
  94. }
  95. void _hlwExpectedPower(unsigned int power) {
  96. if (power > 0) {
  97. _hlw8012.expectedActivePower(power);
  98. _hlwGetCalibration();
  99. }
  100. }
  101. void _hlwExpectedCurrent(double current) {
  102. if (current > 0) {
  103. _hlw8012.expectedCurrent(current);
  104. _hlwGetCalibration();
  105. }
  106. }
  107. void _hlwExpectedVoltage(unsigned int voltage) {
  108. if (voltage > 0) {
  109. _hlw8012.expectedVoltage(voltage);
  110. _hlwGetCalibration();
  111. }
  112. }
  113. #endif
  114. double _powerCurrent() {
  115. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  116. double current = _emon.getCurrent(POWER_SAMPLES);
  117. current -= POWER_CURRENT_OFFSET;
  118. if (current < 0) current = 0;
  119. return current;
  120. #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012
  121. return _hlw8012.getCurrent();
  122. #else
  123. return 0;
  124. #endif
  125. }
  126. double _powerVoltage() {
  127. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  128. return _power_voltage;
  129. #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012
  130. return _hlw8012.getVoltage();
  131. #else
  132. return 0;
  133. #endif
  134. }
  135. #if POWER_HAS_ACTIVE
  136. double _powerActivePower() {
  137. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  138. return _hlw8012.getActivePower();
  139. #else
  140. return 0;
  141. #endif
  142. }
  143. #endif
  144. double _powerApparentPower() {
  145. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  146. return _powerCurrent() * _powerVoltage();
  147. #elif POWER_PROVIDER == POWER_PROVIDER_HLW8012
  148. return _hlw8012.getApparentPower();
  149. #else
  150. return 0;
  151. #endif
  152. }
  153. // -----------------------------------------------------------------------------
  154. // PRIVATE METHODS
  155. // -----------------------------------------------------------------------------
  156. #if WEB_SUPPORT
  157. void _powerAPISetup() {
  158. apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) {
  159. if (_power_ready) {
  160. dtostrf(getCurrent(), len-1, POWER_CURRENT_PRECISION, buffer);
  161. } else {
  162. buffer = NULL;
  163. }
  164. });
  165. apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) {
  166. if (_power_ready) {
  167. snprintf_P(buffer, len, PSTR("%d"), getVoltage());
  168. } else {
  169. buffer = NULL;
  170. }
  171. });
  172. apiRegister(MQTT_TOPIC_APPARENT, MQTT_TOPIC_APPARENT, [](char * buffer, size_t len) {
  173. if (_power_ready) {
  174. snprintf_P(buffer, len, PSTR("%d"), getApparentPower());
  175. } else {
  176. buffer = NULL;
  177. }
  178. });
  179. #if POWER_HAS_ACTIVE
  180. apiRegister(MQTT_TOPIC_POWER, MQTT_TOPIC_POWER, [](char * buffer, size_t len) {
  181. if (_power_ready) {
  182. snprintf_P(buffer, len, PSTR("%d"), getActivePower());
  183. } else {
  184. buffer = NULL;
  185. }
  186. });
  187. #endif
  188. }
  189. #endif // WEB_SUPPORT
  190. void _powerReset() {
  191. _filter_current.reset();
  192. #if POWER_HAS_ACTIVE
  193. _filter_apparent.reset();
  194. _filter_voltage.reset();
  195. _filter_active.reset();
  196. #endif
  197. }
  198. // -----------------------------------------------------------------------------
  199. // MAGNITUDE API
  200. // -----------------------------------------------------------------------------
  201. bool hasActivePower() {
  202. return POWER_HAS_ACTIVE;
  203. }
  204. double getCurrent() {
  205. return _power_current;
  206. }
  207. double getVoltage() {
  208. return _power_voltage;
  209. }
  210. double getApparentPower() {
  211. return _power_apparent;
  212. }
  213. #if POWER_HAS_ACTIVE
  214. double getActivePower() {
  215. return _power_active;
  216. }
  217. double getReactivePower() {
  218. if (_power_apparent > _power_active) {
  219. return sqrt(_power_apparent * _power_apparent - _power_active * _power_active);
  220. }
  221. return 0;
  222. }
  223. double getPowerFactor() {
  224. if (_power_active > _power_apparent) return 1;
  225. if (_power_apparent == 0) return 0;
  226. return (double) _power_active / _power_apparent;
  227. }
  228. #endif
  229. // -----------------------------------------------------------------------------
  230. // PUBLIC API
  231. // -----------------------------------------------------------------------------
  232. bool powerEnabled() {
  233. return _power_enabled;
  234. }
  235. void powerEnabled(bool enabled) {
  236. _power_enabled = enabled;
  237. #if (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && HLW8012_USE_INTERRUPTS
  238. if (_power_enabled) {
  239. attachInterrupt(HLW8012_CF1_PIN, _hlw_cf1_isr, CHANGE);
  240. attachInterrupt(HLW8012_CF_PIN, _hlw_cf_isr, CHANGE);
  241. } else {
  242. detachInterrupt(HLW8012_CF1_PIN);
  243. detachInterrupt(HLW8012_CF_PIN);
  244. }
  245. #endif
  246. }
  247. void powerConfigure() {
  248. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  249. _emon.setCurrentRatio(getSetting("powerRatioC", POWER_CURRENT_RATIO).toFloat());
  250. _power_voltage = getSetting("powerVoltage", POWER_VOLTAGE).toFloat();
  251. #endif
  252. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  253. _hlwSetCalibration();
  254. _hlwGetCalibration();
  255. #endif
  256. }
  257. void powerSetup() {
  258. // backwards compatibility
  259. moveSetting("pwMainsVoltage", "powerVoltage");
  260. moveSetting("emonMains", "powerVoltage");
  261. moveSetting("emonVoltage", "powerVoltage");
  262. moveSetting("pwCurrentRatio", "powerRatioC");
  263. moveSetting("emonRatio", "powerRatioC");
  264. moveSetting("powPowerMult", "powerRatioP");
  265. moveSetting("powCurrentMult", "powerRatioC");
  266. moveSetting("powVoltageMult", "powerRatioV");
  267. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  268. // Initialize HLW8012
  269. // 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);
  270. // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
  271. // * currentWhen is the value in sel_pin to select current sampling
  272. // * set use_interrupts to true to use interrupts to monitor pulse widths
  273. // * leave pulse_timeout to the default value, recommended when using interrupts
  274. #if HLW8012_USE_INTERRUPTS
  275. _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true);
  276. #else
  277. _hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000);
  278. #endif
  279. // These values are used to calculate current, voltage and power factors as per datasheet formula
  280. // These are the nominal values for the Sonoff POW resistors:
  281. // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
  282. // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
  283. // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
  284. _hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
  285. #endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012
  286. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  287. _emon.initCurrent(currentCallback, POWER_ADC_BITS, POWER_REFERENCE_VOLTAGE, POWER_CURRENT_RATIO);
  288. #endif
  289. #if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
  290. uint8_t buffer[2];
  291. buffer[0] = ADC121_REG_CONFIG;
  292. buffer[1] = 0x00;
  293. brzo_i2c_start_transaction(POWER_I2C_ADDRESS, I2C_SCL_FREQUENCY);
  294. brzo_i2c_write(buffer, 2, false);
  295. brzo_i2c_end_transaction();
  296. #endif
  297. powerConfigure();
  298. #if POWER_PROVIDER & POWER_PROVIDER_EMON
  299. _emon.warmup();
  300. #endif
  301. #if POWER_PROVIDER == POWER_PROVIDER_HLW8012
  302. _power_wifi_onconnect = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
  303. powerEnabled(true);
  304. });
  305. _power_wifi_ondisconnect = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected ipInfo) {
  306. powerEnabled(false);
  307. });
  308. #endif
  309. // API
  310. #if WEB_SUPPORT
  311. _powerAPISetup();
  312. #endif
  313. DEBUG_MSG_P(PSTR("[POWER] POWER_PROVIDER = %d\n"), POWER_PROVIDER);
  314. }
  315. void powerLoop() {
  316. static unsigned long last = 0;
  317. static bool was_disabled = false;
  318. if (!_power_enabled) {
  319. was_disabled = true;
  320. return;
  321. }
  322. if (was_disabled) {
  323. was_disabled = false;
  324. last = millis();
  325. _powerReset();
  326. }
  327. if (millis() - last < POWER_INTERVAL) return;
  328. last = millis();
  329. // Get instantaneous values from HAL
  330. double current = _powerCurrent();
  331. double voltage = _powerVoltage();
  332. double apparent = _powerApparentPower();
  333. #if POWER_HAS_ACTIVE
  334. double active = _powerActivePower();
  335. #endif
  336. // Filters
  337. _filter_current.add(current);
  338. #if POWER_HAS_ACTIVE
  339. _filter_apparent.add(apparent);
  340. _filter_voltage.add(voltage);
  341. _filter_active.add(active);
  342. #endif
  343. char current_buffer[10];
  344. dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_PRECISION, current_buffer);
  345. DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer);
  346. DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), voltage);
  347. DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), apparent);
  348. #if POWER_HAS_ACTIVE
  349. DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), active);
  350. DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), getReactivePower());
  351. DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), 100 * getPowerFactor());
  352. #endif
  353. // Update websocket clients
  354. #if WEB_SUPPORT
  355. {
  356. DynamicJsonBuffer jsonBuffer;
  357. JsonObject& root = jsonBuffer.createObject();
  358. root["powerVisible"] = 1;
  359. root["powerCurrent"] = String(current_buffer);
  360. root["powerVoltage"] = voltage;
  361. root["powerApparentPower"] = apparent;
  362. #if POWER_HAS_ACTIVE
  363. root["powerActivePower"] = active;
  364. root["powerReactivePower"] = getReactivePower();
  365. root["powerPowerfactor"] = int(100 * getPowerFactor());
  366. #endif
  367. String output;
  368. root.printTo(output);
  369. wsSend(output.c_str());
  370. }
  371. #endif
  372. // Send MQTT messages averaged every POWER_REPORT_EVERY measurements
  373. if (_filter_current.count() == POWER_REPORT_EVERY) {
  374. // Get the fitered values
  375. _power_current = _filter_current.average(true);
  376. #if POWER_HAS_ACTIVE
  377. _power_apparent = _filter_apparent.average(true);
  378. _power_voltage = _filter_voltage.average(true);
  379. _power_active = _filter_active.average(true);
  380. double power = _power_active;
  381. #else
  382. _power_apparent = _power_current * _power_voltage;
  383. double power = _power_apparent;
  384. #endif
  385. double delta_energy = power * POWER_ENERGY_FACTOR;
  386. char delta_energy_buffer[10];
  387. dtostrf(delta_energy, sizeof(delta_energy_buffer)-1, POWER_CURRENT_PRECISION, delta_energy_buffer);
  388. _power_ready = true;
  389. // Report values to MQTT broker
  390. {
  391. mqttSend(MQTT_TOPIC_CURRENT, current_buffer);
  392. mqttSend(MQTT_TOPIC_APPARENT, String((int) _power_apparent).c_str());
  393. mqttSend(MQTT_TOPIC_ENERGY, delta_energy_buffer);
  394. #if POWER_HAS_ACTIVE
  395. mqttSend(MQTT_TOPIC_POWER, String((int) _power_active).c_str());
  396. mqttSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
  397. #endif
  398. }
  399. // Report values to Domoticz
  400. #if DOMOTICZ_SUPPORT
  401. {
  402. char buffer[20];
  403. snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, delta_energy_buffer);
  404. domoticzSend("dczPowIdx", 0, buffer);
  405. domoticzSend("dczEnergyIdx", 0, delta_energy_buffer);
  406. domoticzSend("dczCurrentIdx", 0, current_buffer);
  407. #if POWER_HAS_ACTIVE
  408. snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _power_voltage);
  409. domoticzSend("dczVoltIdx", 0, buffer);
  410. #endif
  411. }
  412. #endif
  413. #if INFLUXDB_SUPPORT
  414. {
  415. influxDBSend(MQTT_TOPIC_CURRENT, current_buffer);
  416. influxDBSend(MQTT_TOPIC_APPARENT, String((int) _power_apparent).c_str());
  417. influxDBSend(MQTT_TOPIC_ENERGY, delta_energy_buffer);
  418. #if POWER_HAS_ACTIVE
  419. influxDBSend(MQTT_TOPIC_POWER, String((int) _power_active).c_str());
  420. influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
  421. #endif
  422. }
  423. #endif
  424. }
  425. // Toggle between current and voltage monitoring
  426. #if (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && (HLW8012_USE_INTERRUPTS == 0)
  427. _hlw8012.toggleMode();
  428. #endif // (POWER_PROVIDER == POWER_PROVIDER_HLW8012) && (HLW8012_USE_INTERRUPTS == 0)
  429. }
  430. #endif // POWER_PROVIDER != POWER_PROVIDER_NONE