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.

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