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.

318 lines
11 KiB

6 years ago
  1. // -----------------------------------------------------------------------------
  2. // PZEM004T based power monitor
  3. // Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
  4. // -----------------------------------------------------------------------------
  5. // Connection Diagram:
  6. // -------------------
  7. //
  8. // Needed when connecting multiple PZEM004T devices on the same UART
  9. // *You must set the PZEM004T device address prior using this configuration*
  10. //
  11. // +---------+
  12. // | ESPurna | +VCC
  13. // | Node | ^
  14. // | G T R | |
  15. // +-+--+--+-+ R (10K)
  16. // | | | |
  17. // | | +-----------------+---------------+---------------+
  18. // | +-----------------+--|------------+--|------------+ |
  19. // +-----------------+--|--|---------+--|--|---------+ | |
  20. // | | | | | | | | |
  21. // | | V | | V | | V
  22. // | | - | | - | | -
  23. // +-+--+--+-+ +-+--+--+-+ +-+--+--+-+
  24. // | G R T | | G R T | | G R T |
  25. // |PZEM-004T| |PZEM-004T| |PZEM-004T|
  26. // | Module | | Module | | Module |
  27. // +---------+ +---------+ +---------+
  28. //
  29. // Where:
  30. // ------
  31. // G = GND
  32. // R = ESPurna UART RX
  33. // T = ESPurna UART TX
  34. // V = Small Signal Schottky Diode, like BAT43,
  35. // Cathode to PZEM TX, Anode to Espurna RX
  36. // R = Resistor to VCC, 10K
  37. //
  38. // More Info:
  39. // ----------
  40. // See ESPurna Wiki - https://github.com/xoseperez/espurna/wiki/Sensor-PZEM004T
  41. //
  42. // Reference:
  43. // ----------
  44. // UART/TTL-Serial network with single master and multiple slaves:
  45. // http://cool-emerald.blogspot.com/2009/10/multidrop-network-for-rs232.html
  46. #if SENSOR_SUPPORT && PZEM004T_SUPPORT
  47. #pragma once
  48. #include "Arduino.h"
  49. #include "BaseSensor.h"
  50. #include <PZEM004T.h>
  51. #define PZ_MAGNITUDE_COUNT 4
  52. #define PZ_MAGNITUDE_CURRENT_INDEX 0
  53. #define PZ_MAGNITUDE_VOLTAGE_INDEX 1
  54. #define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2
  55. #define PZ_MAGNITUDE_ENERGY_INDEX 3
  56. class PZEM004TSensor : public BaseSensor {
  57. public:
  58. // ---------------------------------------------------------------------
  59. // Public
  60. // ---------------------------------------------------------------------
  61. PZEM004TSensor(): BaseSensor() {
  62. _sensor_id = SENSOR_PZEM004T_ID;
  63. }
  64. ~PZEM004TSensor() {
  65. if (_pzem) delete _pzem;
  66. }
  67. // ---------------------------------------------------------------------
  68. void setRX(unsigned char pin_rx) {
  69. if (_pin_rx == pin_rx) return;
  70. _pin_rx = pin_rx;
  71. _dirty = true;
  72. }
  73. void setTX(unsigned char pin_tx) {
  74. if (_pin_tx == pin_tx) return;
  75. _pin_tx = pin_tx;
  76. _dirty = true;
  77. }
  78. void setSerial(HardwareSerial * serial) {
  79. _serial = serial;
  80. _dirty = true;
  81. }
  82. // Set the devices physical addresses managed by this sensor
  83. void setAddresses(const char *addresses) {
  84. char const * sep = " ";
  85. char tokens[strlen(addresses) + 1];
  86. strlcpy(tokens, addresses, sizeof(tokens));
  87. char *address = tokens;
  88. int i = 0;
  89. address = strtok(address, sep);
  90. while (address != 0 && i++ < PZEM004T_MAX_DEVICES) {
  91. IPAddress addr;
  92. reading_t reading;
  93. reading.current = PZEM_ERROR_VALUE;
  94. reading.voltage = PZEM_ERROR_VALUE;
  95. reading.power = PZEM_ERROR_VALUE;
  96. reading.energy = PZEM_ERROR_VALUE;
  97. if (addr.fromString(address)) {
  98. _devices.push_back(addr);
  99. _energy_offsets.push_back(0);
  100. _readings.push_back(reading);
  101. }
  102. address = strtok(0, sep);
  103. }
  104. _count = _devices.size() * PZ_MAGNITUDE_COUNT;
  105. _dirty = true;
  106. }
  107. // Return the number of devices managed by this sensor
  108. unsigned char getAddressesCount() {
  109. return _devices.size();
  110. }
  111. // Get device physical address based on the device index
  112. String getAddress(unsigned char dev) {
  113. return _devices[dev].toString();
  114. }
  115. // Set the device physical address
  116. bool setDeviceAddress(IPAddress *addr) {
  117. while(_busy) { yield(); };
  118. _busy = true;
  119. bool res = _pzem->setAddress(*addr);
  120. _busy = false;
  121. return res;
  122. }
  123. // ---------------------------------------------------------------------
  124. unsigned char getRX() {
  125. return _pin_rx;
  126. }
  127. unsigned char getTX() {
  128. return _pin_tx;
  129. }
  130. // ---------------------------------------------------------------------
  131. // If called with value = -1, the offset will be the last energy reading
  132. // otherwise, it will be the value provided
  133. float resetEnergy(unsigned char dev, float value = -1) {
  134. _energy_offsets[dev] = value != -1 ? value : _readings[dev].energy;
  135. return _energy_offsets[dev];
  136. }
  137. // ---------------------------------------------------------------------
  138. // Sensor API
  139. // ---------------------------------------------------------------------
  140. // Initialization method, must be idempotent
  141. void begin() {
  142. if (!_dirty) return;
  143. if (_pzem) delete _pzem;
  144. if (_serial) {
  145. _pzem = new PZEM004T(_serial);
  146. } else {
  147. _pzem = new PZEM004T(_pin_rx, _pin_tx);
  148. }
  149. if(_devices.size() == 1) _pzem->setAddress(_devices[0]);
  150. _ready = true;
  151. _dirty = false;
  152. }
  153. // Descriptive name of the sensor
  154. String description() {
  155. char buffer[27];
  156. if (_serial) {
  157. snprintf(buffer, sizeof(buffer), "PZEM004T @ HwSerial");
  158. } else {
  159. snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
  160. }
  161. return String(buffer);
  162. }
  163. // Descriptive name of the slot # index
  164. String slot(unsigned char index) {
  165. int dev = index / PZ_MAGNITUDE_COUNT;
  166. char buffer[25];
  167. snprintf(buffer, sizeof(buffer), "(%u/%s)", dev, getAddress(dev).c_str());
  168. return description() + String(buffer);
  169. };
  170. // Address of the sensor (it could be the GPIO or I2C address)
  171. String address(unsigned char index) {
  172. int dev = index / PZ_MAGNITUDE_COUNT;
  173. return _devices[dev].toString();
  174. }
  175. // Type for slot # index
  176. unsigned char type(unsigned char index) {
  177. int dev = index / PZ_MAGNITUDE_COUNT;
  178. index = index - (dev * PZ_MAGNITUDE_COUNT);
  179. if (index == PZ_MAGNITUDE_CURRENT_INDEX) return MAGNITUDE_CURRENT;
  180. if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) return MAGNITUDE_VOLTAGE;
  181. if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) return MAGNITUDE_POWER_ACTIVE;
  182. if (index == PZ_MAGNITUDE_ENERGY_INDEX) return MAGNITUDE_ENERGY;
  183. return MAGNITUDE_NONE;
  184. }
  185. // Current value for slot # index
  186. double value(unsigned char index) {
  187. int dev = index / PZ_MAGNITUDE_COUNT;
  188. index = index - (dev * PZ_MAGNITUDE_COUNT);
  189. double response = 0;
  190. if (index == PZ_MAGNITUDE_CURRENT_INDEX) response = _readings[dev].current;
  191. if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) response = _readings[dev].voltage;
  192. if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) response = _readings[dev].power;
  193. if (index == PZ_MAGNITUDE_ENERGY_INDEX) response = (_readings[dev].energy * 3600) - _energy_offsets[dev];
  194. if (response < 0) response = 0;
  195. return response;
  196. }
  197. // Post-read hook (usually to reset things)
  198. void post() {
  199. _error = SENSOR_ERROR_OK;
  200. }
  201. // Loop-like method, call it in your main loop
  202. void tick() {
  203. static unsigned char dev = 0;
  204. static unsigned char magnitude = 0;
  205. static unsigned long last_millis = 0;
  206. if (_busy || millis() - last_millis < PZEM004T_READ_INTERVAL) return;
  207. _busy = true;
  208. // Clear buffer in case of late response(Timeout)
  209. if (_serial) {
  210. while(_serial->available() > 0) _serial->read();
  211. } else {
  212. // This we cannot do it from outside the library
  213. }
  214. float read;
  215. float* readings_p;
  216. switch(magnitude) {
  217. case PZ_MAGNITUDE_CURRENT_INDEX:
  218. read = _pzem->current(_devices[dev]);
  219. readings_p = &_readings[dev].current;
  220. break;
  221. case PZ_MAGNITUDE_VOLTAGE_INDEX:
  222. read = _pzem->voltage(_devices[dev]);
  223. readings_p = &_readings[dev].voltage;
  224. break;
  225. case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
  226. read = _pzem->power(_devices[dev]);
  227. readings_p = &_readings[dev].power;
  228. break;
  229. case PZ_MAGNITUDE_ENERGY_INDEX:
  230. read = _pzem->energy(_devices[dev]);
  231. readings_p = &_readings[dev].energy;
  232. break;
  233. default:
  234. _busy = false;
  235. return;
  236. }
  237. if(read == PZEM_ERROR_VALUE) {
  238. _error = SENSOR_ERROR_TIMEOUT;
  239. } else {
  240. *readings_p = read;
  241. }
  242. if(++dev == _devices.size()) {
  243. dev = 0;
  244. last_millis = millis();
  245. if(++magnitude == PZ_MAGNITUDE_COUNT) {
  246. magnitude = 0;
  247. }
  248. }
  249. _busy = false;
  250. }
  251. protected:
  252. // ---------------------------------------------------------------------
  253. // Protected
  254. // ---------------------------------------------------------------------
  255. unsigned int _pin_rx = PZEM004T_RX_PIN;
  256. unsigned int _pin_tx = PZEM004T_TX_PIN;
  257. bool _busy = false;
  258. typedef struct {
  259. float voltage;
  260. float current;
  261. float power;
  262. float energy;
  263. } reading_t;
  264. std::vector<reading_t> _readings;
  265. std::vector<float> _energy_offsets;
  266. std::vector<IPAddress> _devices;
  267. HardwareSerial * _serial = NULL;
  268. PZEM004T * _pzem = NULL;
  269. };
  270. #endif // SENSOR_SUPPORT && PZEM004T_SUPPORT