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.

315 lines
8.9 KiB

  1. /*
  2. V9261F MODULE
  3. Support for V9261D-based power monitors
  4. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  5. */
  6. #if V9261F_SUPPORT
  7. #include <SoftwareSerial.h>
  8. #include <ArduinoJson.h>
  9. SoftwareSerial * _v9261f_uart;
  10. bool _v9261f_enabled = false;
  11. bool _v9261f_ready = false;
  12. bool _v9261f_newdata = false;
  13. int _v9261f_power = 0;
  14. int _v9261f_rpower = 0;
  15. int _v9261f_voltage = 0;
  16. double _v9261f_current = 0;
  17. unsigned char _v9261f_data[24];
  18. // -----------------------------------------------------------------------------
  19. // PRIVATE
  20. // -----------------------------------------------------------------------------
  21. void v9261fRead() {
  22. static unsigned char state = 0;
  23. static unsigned long last = 0;
  24. static bool found = false;
  25. static unsigned char index = 0;
  26. if (state == 0) {
  27. while (_v9261f_uart->available()) {
  28. _v9261f_uart->flush();
  29. found = true;
  30. last = millis();
  31. }
  32. if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
  33. _v9261f_uart->flush();
  34. index = 0;
  35. state = 1;
  36. }
  37. } else if (state == 1) {
  38. while (_v9261f_uart->available()) {
  39. _v9261f_uart->read();
  40. if (index++ >= 7) {
  41. _v9261f_uart->flush();
  42. index = 0;
  43. state = 2;
  44. }
  45. }
  46. } else if (state == 2) {
  47. while (_v9261f_uart->available()) {
  48. _v9261f_data[index] = _v9261f_uart->read();
  49. if (index++ >= 19) {
  50. _v9261f_uart->flush();
  51. last = millis();
  52. state = 3;
  53. }
  54. }
  55. } else if (state == 3) {
  56. /*
  57. for (unsigned char i=0; i<index;i++) {
  58. DEBUG_MSG("%02X ", _v9261f_data[i]);
  59. }
  60. DEBUG_MSG("\n");
  61. */
  62. if (checksumOK()) {
  63. _v9261f_power = (double) (
  64. (_v9261f_data[3]) +
  65. (_v9261f_data[4] << 8) +
  66. (_v9261f_data[5] << 16) +
  67. (_v9261f_data[6] << 24)
  68. ) / V9261F_POWER_FACTOR;
  69. _v9261f_rpower = (double) (
  70. (_v9261f_data[7]) +
  71. (_v9261f_data[8] << 8) +
  72. (_v9261f_data[9] << 16) +
  73. (_v9261f_data[10] << 24)
  74. ) / V9261F_RPOWER_FACTOR;
  75. _v9261f_voltage = (double) (
  76. (_v9261f_data[11]) +
  77. (_v9261f_data[12] << 8) +
  78. (_v9261f_data[13] << 16) +
  79. (_v9261f_data[14] << 24)
  80. ) / V9261F_VOLTAGE_FACTOR;
  81. _v9261f_current = (double) (
  82. (_v9261f_data[15]) +
  83. (_v9261f_data[16] << 8) +
  84. (_v9261f_data[17] << 16) +
  85. (_v9261f_data[18] << 24)
  86. ) / V9261F_CURRENT_FACTOR;
  87. _v9261f_newdata = true;
  88. /*
  89. DEBUG_MSG_P(PSTR("[V9261F] W = %lu\n"), _v9261f_power);
  90. DEBUG_MSG_P(PSTR("[V9261F] R = %lu\n"), _v9261f_rpower);
  91. DEBUG_MSG_P(PSTR("[V9261F] V = %lu\n"), _v9261f_voltage);
  92. DEBUG_MSG_P(PSTR("[V9261F] C = %lu\n"), _v9261f_current);
  93. */
  94. }
  95. last = millis();
  96. index = 0;
  97. state = 4;
  98. } else if (state == 4) {
  99. while (_v9261f_uart->available()) {
  100. _v9261f_uart->flush();
  101. last = millis();
  102. }
  103. if (millis() - last > V9261F_SYNC_INTERVAL) {
  104. state = 1;
  105. }
  106. }
  107. }
  108. boolean checksumOK() {
  109. unsigned char checksum = 0;
  110. for (unsigned char i = 0; i < 19; i++) {
  111. checksum = checksum + _v9261f_data[i];
  112. }
  113. checksum = ~checksum + 0x33;
  114. return checksum == _v9261f_data[19];
  115. }
  116. // -----------------------------------------------------------------------------
  117. // HAL
  118. // -----------------------------------------------------------------------------
  119. unsigned int getActivePower() {
  120. return _v9261f_power;
  121. }
  122. unsigned int getReactivePower() {
  123. return _v9261f_rpower;
  124. }
  125. unsigned int getApparentPower() {
  126. return sqrt(_v9261f_rpower * _v9261f_rpower + _v9261f_power * _v9261f_power);
  127. }
  128. unsigned int getVoltage() {
  129. return _v9261f_voltage;
  130. }
  131. double getCurrent() {
  132. return _v9261f_current;
  133. }
  134. double getPowerFactor() {
  135. unsigned int apparent = getApparentPower();
  136. if (apparent > 0) return getActivePower() / getApparentPower();
  137. return 1;
  138. }
  139. // -----------------------------------------------------------------------------
  140. void v9261fSetup() {
  141. _v9261f_uart = new SoftwareSerial(V9261F_PIN, SW_SERIAL_UNUSED_PIN, V9261F_PIN_INVERSE, 256);
  142. _v9261f_uart->begin(V9261F_BAUDRATE);
  143. // API definitions
  144. #if WEB_SUPPORT
  145. apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
  146. snprintf_P(buffer, len, PSTR("%d"), _v9261f_power);
  147. });
  148. apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
  149. dtostrf(_v9261f_current, 1-len, 3, buffer);
  150. });
  151. apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
  152. snprintf_P(buffer, len, PSTR("%d"), _v9261f_voltage);
  153. });
  154. #endif // WEB_SUPPORT
  155. }
  156. void v9261fLoop() {
  157. static int sum_power = 0;
  158. static int sum_rpower = 0;
  159. static int sum_voltage = 0;
  160. static double sum_current = 0;
  161. static int count = 0;
  162. // Sniff data in the UART interface
  163. v9261fRead();
  164. // Do we have new data?
  165. if (_v9261f_newdata) {
  166. _v9261f_newdata = false;
  167. sum_power += getActivePower();
  168. sum_rpower += getReactivePower();
  169. sum_voltage += getVoltage();
  170. sum_current += getCurrent();
  171. count++;
  172. #if WEB_SUPPORT
  173. {
  174. DynamicJsonBuffer jsonBuffer;
  175. JsonObject& root = jsonBuffer.createObject();
  176. char buf_current[10];
  177. dtostrf(getCurrent(), 1-sizeof(buf_current), 3, buf_current);
  178. root["powVisible"] = 1;
  179. root["powActivePower"] = getActivePower();
  180. root["powCurrent"] = String(buf_current);
  181. root["powVoltage"] = getVoltage();
  182. root["powApparentPower"] = getApparentPower();
  183. root["powReactivePower"] = getReactivePower();
  184. root["powPowerFactor"] = 100 * getPowerFactor();
  185. String output;
  186. root.printTo(output);
  187. wsSend(output.c_str());
  188. }
  189. #endif
  190. }
  191. // Do we have to report?
  192. static unsigned long last = 0;
  193. if ((count == 0) || (millis() - last < V9261F_REPORT_INTERVAL)) return;
  194. last = millis();
  195. {
  196. unsigned int power = sum_power / count;
  197. unsigned int reactive = sum_rpower / count;
  198. unsigned int voltage = sum_voltage / count;
  199. double current = sum_current / count;
  200. char buf_current[10];
  201. dtostrf(current, 1-sizeof(buf_current), 3, buf_current);
  202. unsigned int apparent = sqrt(power * power + reactive * reactive);
  203. double energy_delta = (double) power * V9261F_REPORT_INTERVAL / 1000.0 / 3600.0;
  204. char buf_energy[10];
  205. dtostrf(energy_delta, 1-sizeof(buf_energy), 3, buf_energy);
  206. unsigned int factor = 100 * ((double) power / apparent);
  207. // Report values to MQTT broker
  208. mqttSend(HLW8012_POWER_TOPIC, String(power).c_str());
  209. mqttSend(HLW8012_CURRENT_TOPIC, buf_current);
  210. mqttSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str());
  211. mqttSend(HLW8012_ENERGY_TOPIC, buf_energy);
  212. mqttSend(HLW8012_APOWER_TOPIC, String(apparent).c_str());
  213. mqttSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str());
  214. mqttSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str());
  215. // Report values to Domoticz
  216. #if DOMOTICZ_SUPPORT
  217. {
  218. char buffer[20];
  219. snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, buf_energy);
  220. domoticzSend("dczPowIdx", 0, buffer);
  221. snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_energy);
  222. domoticzSend("dczEnergyIdx", 0, buffer);
  223. snprintf_P(buffer, sizeof(buffer), PSTR("%d"), voltage);
  224. domoticzSend("dczVoltIdx", 0, buffer);
  225. snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_current);
  226. domoticzSend("dczCurrentIdx", 0, buffer);
  227. }
  228. #endif
  229. #if INFLUXDB_SUPPORT
  230. {
  231. influxDBSend(HLW8012_POWER_TOPIC, String(power).c_str());
  232. influxDBSend(HLW8012_CURRENT_TOPIC, buf_current);
  233. influxDBSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str());
  234. influxDBSend(HLW8012_ENERGY_TOPIC, buf_energy);
  235. influxDBSend(HLW8012_APOWER_TOPIC, String(apparent).c_str());
  236. influxDBSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str());
  237. influxDBSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str());
  238. }
  239. #endif
  240. // Reset counters
  241. sum_power = sum_rpower = sum_voltage = sum_current = count = 0;
  242. }
  243. }
  244. #endif