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.

427 lines
14 KiB

  1. // -----------------------------------------------------------------------------
  2. // PZEM004T based power monitor
  3. // Copyright (C) 2019 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 <PZEM004T.h>
  50. #include "BaseSensor.h"
  51. #include "BaseEmonSensor.h"
  52. #include "../sensor.h"
  53. #include "../terminal.h"
  54. #define PZ_MAGNITUDE_COUNT 4
  55. #define PZ_MAGNITUDE_CURRENT_INDEX 0
  56. #define PZ_MAGNITUDE_VOLTAGE_INDEX 1
  57. #define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2
  58. #define PZ_MAGNITUDE_ENERGY_INDEX 3
  59. class PZEM004TSensor : public BaseEmonSensor {
  60. private:
  61. // We can only create a single instance of the sensor class.
  62. PZEM004TSensor() : BaseEmonSensor(0) {
  63. _sensor_id = SENSOR_PZEM004T_ID;
  64. }
  65. ~PZEM004TSensor() {
  66. if (_pzem) delete _pzem;
  67. PZEM004TSensor::instance = nullptr;
  68. }
  69. public:
  70. static PZEM004TSensor* instance;
  71. static PZEM004TSensor* create() {
  72. if (PZEM004TSensor::instance) return PZEM004TSensor::instance;
  73. PZEM004TSensor::instance = new PZEM004TSensor();
  74. return PZEM004TSensor::instance;
  75. }
  76. // We can't modify PZEM values, just ignore this
  77. void resetEnergy() {}
  78. void resetEnergy(unsigned char) {}
  79. void resetEnergy(unsigned char, sensor::Energy) {}
  80. // Override Base methods that deal with _energy[]
  81. size_t countDevices() {
  82. return _addresses.size();
  83. }
  84. double getEnergy(unsigned char index) {
  85. return _readings[index].energy;
  86. }
  87. sensor::Energy totalEnergy(unsigned char index) {
  88. return getEnergy(index);
  89. }
  90. // ---------------------------------------------------------------------
  91. void setRX(unsigned char pin_rx) {
  92. if (_pin_rx == pin_rx) return;
  93. _pin_rx = pin_rx;
  94. _dirty = true;
  95. }
  96. void setTX(unsigned char pin_tx) {
  97. if (_pin_tx == pin_tx) return;
  98. _pin_tx = pin_tx;
  99. _dirty = true;
  100. }
  101. void setSerial(HardwareSerial * serial) {
  102. _serial = serial;
  103. _dirty = true;
  104. }
  105. // Set the devices physical addresses managed by this sensor
  106. void setAddresses(const char *addresses) {
  107. char const * sep = " ";
  108. char tokens[strlen(addresses) + 1];
  109. strlcpy(tokens, addresses, sizeof(tokens));
  110. char *address = tokens;
  111. int i = 0;
  112. address = strtok(address, sep);
  113. while (address != 0 && i++ < PZEM004T_MAX_DEVICES) {
  114. IPAddress addr;
  115. reading_t reading;
  116. reading.current = PZEM_ERROR_VALUE;
  117. reading.voltage = PZEM_ERROR_VALUE;
  118. reading.power = PZEM_ERROR_VALUE;
  119. reading.energy = PZEM_ERROR_VALUE;
  120. if (addr.fromString(address)) {
  121. _addresses.push_back(addr);
  122. _readings.push_back(reading);
  123. }
  124. address = strtok(0, sep);
  125. }
  126. _count = _addresses.size() * PZ_MAGNITUDE_COUNT;
  127. _dirty = true;
  128. }
  129. // Get device physical address based on the device index
  130. String getAddress(unsigned char dev) {
  131. return _addresses[dev].toString();
  132. }
  133. // Set the device physical address
  134. bool setDeviceAddress(IPAddress *addr) {
  135. while(_busy) { yield(); };
  136. _busy = true;
  137. bool res = _pzem->setAddress(*addr);
  138. _busy = false;
  139. return res;
  140. }
  141. // ---------------------------------------------------------------------
  142. unsigned char getRX() {
  143. return _pin_rx;
  144. }
  145. unsigned char getTX() {
  146. return _pin_tx;
  147. }
  148. // ---------------------------------------------------------------------
  149. // Sensor API
  150. // ---------------------------------------------------------------------
  151. // Initialization method, must be idempotent
  152. void begin() {
  153. if (!_dirty) return;
  154. if (_pzem) delete _pzem;
  155. if (_serial) {
  156. _pzem = new PZEM004T(_serial);
  157. } else {
  158. _pzem = new PZEM004T(_pin_rx, _pin_tx);
  159. }
  160. if(_addresses.size() == 1) _pzem->setAddress(_addresses[0]);
  161. _ready = true;
  162. _dirty = false;
  163. }
  164. // Descriptive name of the sensor
  165. String description() {
  166. char buffer[27];
  167. if (_serial) {
  168. snprintf(buffer, sizeof(buffer), "PZEM004T @ HwSerial");
  169. } else {
  170. snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
  171. }
  172. return String(buffer);
  173. }
  174. // Descriptive name of the slot # index
  175. String slot(unsigned char index) {
  176. int dev = index / PZ_MAGNITUDE_COUNT;
  177. char buffer[25];
  178. snprintf(buffer, sizeof(buffer), "(%u/%s)", dev, getAddress(dev).c_str());
  179. return description() + String(buffer);
  180. };
  181. // Address of the sensor (it could be the GPIO or I2C address)
  182. String address(unsigned char index) {
  183. int dev = index / PZ_MAGNITUDE_COUNT;
  184. return _addresses[dev].toString();
  185. }
  186. // Type for slot # index
  187. unsigned char type(unsigned char index) {
  188. int dev = index / PZ_MAGNITUDE_COUNT;
  189. index = index - (dev * PZ_MAGNITUDE_COUNT);
  190. if (index == PZ_MAGNITUDE_CURRENT_INDEX) return MAGNITUDE_CURRENT;
  191. if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) return MAGNITUDE_VOLTAGE;
  192. if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) return MAGNITUDE_POWER_ACTIVE;
  193. if (index == PZ_MAGNITUDE_ENERGY_INDEX) return MAGNITUDE_ENERGY;
  194. return MAGNITUDE_NONE;
  195. }
  196. // Current value for slot # index
  197. double value(unsigned char index) {
  198. double response = 0.0;
  199. int dev = index / PZ_MAGNITUDE_COUNT;
  200. index = index - (dev * PZ_MAGNITUDE_COUNT);
  201. switch (index) {
  202. case PZ_MAGNITUDE_CURRENT_INDEX:
  203. response = _readings[dev].current;
  204. break;
  205. case PZ_MAGNITUDE_VOLTAGE_INDEX:
  206. response = _readings[dev].voltage;
  207. break;
  208. case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
  209. response = _readings[dev].power;
  210. break;
  211. case PZ_MAGNITUDE_ENERGY_INDEX: {
  212. response = _readings[dev].energy;
  213. break;
  214. }
  215. default:
  216. break;
  217. }
  218. if (response < 0.0) {
  219. response = 0.0;
  220. }
  221. return response;
  222. }
  223. // Post-read hook (usually to reset things)
  224. void post() {
  225. _error = SENSOR_ERROR_OK;
  226. }
  227. // Loop-like method, call it in your main loop
  228. void tick() {
  229. static unsigned char dev = 0;
  230. static unsigned char magnitude = 0;
  231. static unsigned long last_millis = 0;
  232. if (_busy || millis() - last_millis < PZEM004T_READ_INTERVAL) return;
  233. _busy = true;
  234. // Clear buffer in case of late response(Timeout)
  235. if (_serial) {
  236. while(_serial->available() > 0) _serial->read();
  237. } else {
  238. // This we cannot do it from outside the library
  239. }
  240. tickStoreReading(dev, magnitude);
  241. if(++dev == _addresses.size()) {
  242. dev = 0;
  243. last_millis = millis();
  244. if(++magnitude == PZ_MAGNITUDE_COUNT) {
  245. magnitude = 0;
  246. }
  247. }
  248. _busy = false;
  249. }
  250. protected:
  251. // ---------------------------------------------------------------------
  252. // Protected
  253. // ---------------------------------------------------------------------
  254. void tickStoreReading(unsigned char dev, unsigned char magnitude) {
  255. float read = PZEM_ERROR_VALUE;
  256. float* readings_p = nullptr;
  257. switch (magnitude) {
  258. case PZ_MAGNITUDE_CURRENT_INDEX:
  259. read = _pzem->current(_addresses[dev]);
  260. readings_p = &_readings[dev].current;
  261. break;
  262. case PZ_MAGNITUDE_VOLTAGE_INDEX:
  263. read = _pzem->voltage(_addresses[dev]);
  264. readings_p = &_readings[dev].voltage;
  265. break;
  266. case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
  267. read = _pzem->power(_addresses[dev]);
  268. readings_p = &_readings[dev].power;
  269. break;
  270. case PZ_MAGNITUDE_ENERGY_INDEX:
  271. read = _pzem->energy(_addresses[dev]);
  272. readings_p = &_readings[dev].energy;
  273. break;
  274. default:
  275. _busy = false;
  276. return;
  277. }
  278. if (read == PZEM_ERROR_VALUE) {
  279. _error = SENSOR_ERROR_TIMEOUT;
  280. } else {
  281. *readings_p = read;
  282. }
  283. }
  284. struct reading_t {
  285. float voltage;
  286. float current;
  287. float power;
  288. float energy;
  289. };
  290. unsigned int _pin_rx = PZEM004T_RX_PIN;
  291. unsigned int _pin_tx = PZEM004T_TX_PIN;
  292. bool _busy = false;
  293. std::vector<reading_t> _readings;
  294. std::vector<IPAddress> _addresses;
  295. HardwareSerial * _serial = NULL;
  296. PZEM004T * _pzem = NULL;
  297. };
  298. PZEM004TSensor* PZEM004TSensor::instance = nullptr;
  299. #if TERMINAL_SUPPORT
  300. void pzem004tInitCommands() {
  301. terminalRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
  302. if (!PZEM004TSensor::instance) return;
  303. if (e->argc == 1) {
  304. DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
  305. unsigned char dev_count = PZEM004TSensor::instance->countDevices();
  306. for(unsigned char dev = 0; dev < dev_count; dev++) {
  307. DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, PZEM004TSensor::instance->getAddress(dev).c_str());
  308. }
  309. terminalOK();
  310. } else if(e->argc == 2) {
  311. IPAddress addr;
  312. if (addr.fromString(String(e->argv[1]))) {
  313. if(PZEM004TSensor::instance->setDeviceAddress(&addr)) {
  314. terminalOK();
  315. }
  316. } else {
  317. terminalError(F("Invalid address argument"));
  318. }
  319. } else {
  320. terminalError(F("Wrong arguments"));
  321. }
  322. });
  323. terminalRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
  324. if(e->argc > 2) {
  325. terminalError(F("Wrong arguments"));
  326. } else {
  327. unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
  328. unsigned char limit = e->argc == 2 ? init +1 : PZEM004TSensor::instance->countDevices();
  329. DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
  330. for(unsigned char dev = init; dev < limit; dev++) {
  331. PZEM004TSensor::instance->resetEnergy(dev);
  332. }
  333. terminalOK();
  334. }
  335. });
  336. terminalRegisterCommand(F("PZ.VALUE"), [](Embedis* e) {
  337. if(e->argc > 2) {
  338. terminalError(F("Wrong arguments"));
  339. } else {
  340. unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
  341. unsigned char limit = e->argc == 2 ? init +1 : PZEM004TSensor::instance->countDevices();
  342. DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
  343. for(unsigned char dev = init; dev < limit; dev++) {
  344. DEBUG_MSG_P(PSTR("Device %d/%s - Current: %s Voltage: %s Power: %s Energy: %s\n"), //
  345. dev,
  346. PZEM004TSensor::instance->getAddress(dev).c_str(),
  347. String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_CURRENT_INDEX)).c_str(),
  348. String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_VOLTAGE_INDEX)).c_str(),
  349. String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(),
  350. String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str());
  351. }
  352. terminalOK();
  353. }
  354. });
  355. }
  356. #endif // TERMINAL_SUPPORT == 1
  357. #endif // SENSOR_SUPPORT && PZEM004T_SUPPORT