xnrg_03_pzem004t.ino 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*
  2. xnrg_03_pzem004t.ino - PZEM004T energy sensor support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_ENERGY_SENSOR
  16. #ifdef USE_PZEM004T
  17. /*********************************************************************************************\
  18. * PZEM004T - Energy
  19. *
  20. * Source: Victor Ferrer https://github.com/vicfergar/Sonoff-MQTT-OTA-Arduino
  21. * Based on: PZEM004T library https://github.com/olehs/PZEM004T
  22. *
  23. * Hardware Serial will be selected if GPIO1 = [63 PZEM004 Rx] and GPIO3 = [62 PZEM0XX Tx]
  24. \*********************************************************************************************/
  25. #define XNRG_03 3
  26. #include <TasmotaSerial.h>
  27. TasmotaSerial *PzemSerial;
  28. #define PZEM_VOLTAGE (uint8_t)0xB0
  29. #define RESP_VOLTAGE (uint8_t)0xA0
  30. #define PZEM_CURRENT (uint8_t)0xB1
  31. #define RESP_CURRENT (uint8_t)0xA1
  32. #define PZEM_POWER (uint8_t)0xB2
  33. #define RESP_POWER (uint8_t)0xA2
  34. #define PZEM_ENERGY (uint8_t)0xB3
  35. #define RESP_ENERGY (uint8_t)0xA3
  36. #define PZEM_SET_ADDRESS (uint8_t)0xB4
  37. #define RESP_SET_ADDRESS (uint8_t)0xA4
  38. #define PZEM_POWER_ALARM (uint8_t)0xB5
  39. #define RESP_POWER_ALARM (uint8_t)0xA5
  40. #define PZEM_DEFAULT_READ_TIMEOUT 500
  41. /*********************************************************************************************/
  42. struct PZEMCommand {
  43. uint8_t command;
  44. uint8_t addr[4];
  45. uint8_t data;
  46. uint8_t crc;
  47. };
  48. IPAddress pzem_ip(192, 168, 1, 1);
  49. uint8_t PzemCrc(uint8_t *data)
  50. {
  51. uint16_t crc = 0;
  52. for (uint8_t i = 0; i < sizeof(PZEMCommand) -1; i++) crc += *data++;
  53. return (uint8_t)(crc & 0xFF);
  54. }
  55. void PzemSend(uint8_t cmd)
  56. {
  57. PZEMCommand pzem;
  58. pzem.command = cmd;
  59. for (uint8_t i = 0; i < sizeof(pzem.addr); i++) pzem.addr[i] = pzem_ip[i];
  60. pzem.data = 0;
  61. uint8_t *bytes = (uint8_t*)&pzem;
  62. pzem.crc = PzemCrc(bytes);
  63. PzemSerial->flush();
  64. PzemSerial->write(bytes, sizeof(pzem));
  65. }
  66. bool PzemReceiveReady(void)
  67. {
  68. return PzemSerial->available() >= (int)sizeof(PZEMCommand);
  69. }
  70. bool PzemRecieve(uint8_t resp, float *data)
  71. {
  72. // 0 1 2 3 4 5 6
  73. // A4 00 00 00 00 00 A4 - Set address
  74. // A0 00 D4 07 00 00 7B - Voltage (212.7V)
  75. // A1 00 00 0A 00 00 AB - Current (0.1A)
  76. // A1 00 00 00 00 00 A1 - No current
  77. // A2 00 16 00 00 00 B8 - Power (22W)
  78. // A2 00 00 00 00 00 A2 - No power
  79. // A3 00 08 A4 00 00 4F - Energy (2.212kWh)
  80. // A3 01 86 9F 00 00 C9 - Energy (99.999kWh)
  81. uint8_t buffer[sizeof(PZEMCommand)] = { 0 };
  82. unsigned long start = millis();
  83. uint8_t len = 0;
  84. while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) {
  85. if (PzemSerial->available() > 0) {
  86. uint8_t c = (uint8_t)PzemSerial->read();
  87. if (!c && !len) {
  88. continue; // skip 0 at startup
  89. }
  90. if ((1 == len) && (buffer[0] == c)) {
  91. len--;
  92. continue; // fix skewed data
  93. }
  94. buffer[len++] = c;
  95. }
  96. }
  97. AddLogSerial(LOG_LEVEL_DEBUG_MORE, buffer, len);
  98. if (len != sizeof(PZEMCommand)) {
  99. // AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem comms timeout"));
  100. return false;
  101. }
  102. if (buffer[6] != PzemCrc(buffer)) {
  103. // AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem crc error"));
  104. return false;
  105. }
  106. if (buffer[0] != resp) {
  107. // AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem bad response"));
  108. return false;
  109. }
  110. switch (resp) {
  111. case RESP_VOLTAGE:
  112. *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); // 65535.x V
  113. break;
  114. case RESP_CURRENT:
  115. *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); // 65535.xx A
  116. break;
  117. case RESP_POWER:
  118. *data = (float)(buffer[1] << 8) + buffer[2]; // 65535 W
  119. break;
  120. case RESP_ENERGY:
  121. *data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; // 16777215 Wh
  122. break;
  123. }
  124. return true;
  125. }
  126. /*********************************************************************************************/
  127. const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY };
  128. const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY };
  129. uint8_t pzem_read_state = 0;
  130. uint8_t pzem_sendRetry = 0;
  131. void PzemEvery200ms(void)
  132. {
  133. bool data_ready = PzemReceiveReady();
  134. if (data_ready) {
  135. float value = 0;
  136. if (PzemRecieve(pzem_responses[pzem_read_state], &value)) {
  137. switch (pzem_read_state) {
  138. case 1: // Voltage as 230.2V
  139. energy_voltage = value;
  140. break;
  141. case 2: // Current as 17.32A
  142. energy_current = value;
  143. break;
  144. case 3: // Power as 20W
  145. energy_active_power = value;
  146. break;
  147. case 4: // Total energy as 99999Wh
  148. if (!energy_start || (value < energy_start)) energy_start = value; // Init after restart and hanlde roll-over if any
  149. if (value != energy_start) {
  150. energy_kWhtoday += (unsigned long)((value - energy_start) * 100);
  151. energy_start = value;
  152. }
  153. EnergyUpdateToday();
  154. break;
  155. }
  156. pzem_read_state++;
  157. if (5 == pzem_read_state) pzem_read_state = 1;
  158. }
  159. }
  160. if (0 == pzem_sendRetry || data_ready) {
  161. pzem_sendRetry = 5;
  162. PzemSend(pzem_commands[pzem_read_state]);
  163. }
  164. else {
  165. pzem_sendRetry--;
  166. }
  167. }
  168. void PzemSnsInit(void)
  169. {
  170. // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions
  171. PzemSerial = new TasmotaSerial(pin[GPIO_PZEM004_RX], pin[GPIO_PZEM0XX_TX], 1);
  172. if (PzemSerial->begin(9600)) {
  173. if (PzemSerial->hardwareSerial()) { ClaimSerial(); }
  174. } else {
  175. energy_flg = ENERGY_NONE;
  176. }
  177. }
  178. void PzemDrvInit(void)
  179. {
  180. if (!energy_flg) {
  181. if ((pin[GPIO_PZEM004_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { // Any device with a Pzem004T
  182. energy_flg = XNRG_03;
  183. }
  184. }
  185. }
  186. /*********************************************************************************************\
  187. * Interface
  188. \*********************************************************************************************/
  189. int Xnrg03(byte function)
  190. {
  191. int result = 0;
  192. if (FUNC_PRE_INIT == function) {
  193. PzemDrvInit();
  194. }
  195. else if (XNRG_03 == energy_flg) {
  196. switch (function) {
  197. case FUNC_INIT:
  198. PzemSnsInit();
  199. break;
  200. case FUNC_EVERY_200_MSECOND:
  201. PzemEvery200ms();
  202. break;
  203. }
  204. }
  205. return result;
  206. }
  207. #endif // USE_PZEM004T
  208. #endif // USE_ENERGY_SENSOR