-
Notifications
You must be signed in to change notification settings - Fork 16
/
espem.h
400 lines (306 loc) · 9.5 KB
/
espem.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/* ESPEM - ESP Energy monitor
* A code for ESP8266/ESP32 based boards to interface with PeaceFair PZEM PowerMeters
* It can poll/collect PowerMeter data and provide it for futher processing in text/json format
*
* (c) Emil Muratov 2018-2021 https://github.com/vortigont/espem
*
*/
#include <vector>
#include <memory>
#include "main.h"
// Libs
#ifdef USE_PZEMv3
#include <PZEM004Tv30.h>
#else
#include <PZEM004T.h>
#endif
// Tasker object from EmbUI
#include "ts.h"
// Defaults
#ifndef ESPEM_POLLRATE
#define ESPEM_POLLRATE 10 // sec
#endif
#ifndef ESPEM_MAXPOLLRATE
#define ESPEM_MAXPOLLRATE 1000 // ms
#endif
#ifndef ESPEM_IPADDR
#define ESPEM_IPADDR {192,168,1,1}
#endif
#ifndef ESPEM_MEMPOOL
#define ESPEM_MEMPOOL 0 // KiB (Do not collect metrics by default)
#endif
#define ESPEM_MEMRESERVE (4*1024U) // Bytes
// Metrics collector state
enum class mcstate_t{MC_DISABLE=0, MC_RUN, MC_PAUSE};
// TaskScheduler - Let the runner object be a global, single instance shared between object files.
extern Scheduler ts;
struct ESPEM_CFG {
uint16_t pollrate; // meter polling interval, s
uint16_t mempool; // desired RAM pool to keep metrics, KiB
IPAddress ip;
/**
* Power Factor correction
* An older PZEM applies some sort of averaging to current and power
* values that might not always correlate to each other properly resulting in PF being > 1
* this enables 'current' value correction in favor of 'power' value
*/
bool PFfix; // Power Factor correction
/**
* Meter polling active/disabled
*/
bool poll;
/**
* metrics collector state
*/
mcstate_t collector;
// struct constructor with default values
ESPEM_CFG (
uint16_t _pp = ESPEM_POLLRATE,
uint16_t _mempool = 0,
bool _pfix = true,
bool _poll = true,
mcstate_t _col = mcstate_t::MC_DISABLE,
IPAddress _ip = ESPEM_IPADDR
) : pollrate(_pp),
mempool(_mempool),
ip(_ip),
PFfix(_pfix),
poll(_poll),
collector(_col) {}
}; // end of ESPEM_CFG structure
struct pmeterData {
union {
struct {
float voltage, current, power, energy;
};
float meterings[4] = {0.0f};
};
// calculate PowerFactor from UIP values
float pf() const {
// result[] must be an array of UIP; PF = P / UI
if (voltage == 0 || current == 0) return 0.0;
return( power / voltage / current);
}
};
#ifdef USE_PZEMv3
class PZEM: public PZEM004Tv30 {
using PZEM004Tv30::PZEM004Tv30;
};
//declare pointer-to-member function for PZEM class
typedef float( PZEM::*PZPTMF)();
#else
class PZEM: public PZEM004T {
using PZEM004T::PZEM004T;
};
//declare pointer-to-member function for PZEM class
typedef float( PZEM::*PZPTMF) (const IPAddress& );
#endif
/**
* @brief - PowerMeter instance class
* Helds an object of one PZEM instance along with it's meterings
*/
class PMETER {
public:
/**
* Class constructor
* uses predefined values of a ESPEM_CFG
*/
PMETER(IPAddress _ip) : ip(_ip){}
~PMETER(){
#ifdef ESPEM_USE_HWSERIAL
delete hwser;
#endif
}
bool pffix = true;
void begin();
unsigned long getLastPollTime(){ return lastpoll; }
const pmeterData &getData() const { return pdata; }
bool poll() { return pollMeter(pzem, pdata, pffix); }
private:
#ifdef ESPEM_USE_HWSERIAL
HardwareSerial *hwser;
#endif
// An instance of PZEM lib object
std::unique_ptr<PZEM> pzem = nullptr;
IPAddress ip = ESPEM_IPADDR;
// PZEM meter status - connected and available
bool pzemrdy = false;
// meter last poll time (system millis())
unsigned long lastpoll = 0;
//pointer array to lib funtions
PZPTMF pzdatafunc[4] = {&PZEM::voltage, &PZEM::current, &PZEM::power, &PZEM::energy};
//a struct for meter data
pmeterData pdata;
// Private Methods
bool pzeminit();
bool pollMeter(std::unique_ptr<PZEM> &meter, pmeterData &result, bool fixpf);
};
/**
* @brief - Power Metrics collector
*
*/
class PMETRICS {
public:
PMETRICS(std::shared_ptr<PMETER> _mtr, uint16_t _psize, uint16_t _rate = ESPEM_POLLRATE, bool _poll = false, bool _collect = false) : meter(_mtr), poolsize(_psize) {
tPoller.set(_rate * TASK_SECOND, TASK_FOREVER, std::bind(&PMETRICS::datapoller, this));
ts.addTask(tPoller);
// enable polling scheduler
if (_poll)
tPoller.enableDelayed();
if (_collect)
poolAlloc(poolsize);
};
~PMETRICS(){
ts.deleteTask(tPoller);
delete samples;
};
bool begin();
inline bool polling(){return tPoller.isEnabled();};
/**
* @brief - Control meter polling
* @param state - enable/disable
*/
bool polling(bool state){
state ? tPoller.enableDelayed() : tPoller.disable();
return polling();
};
/**
* @brief - set polling period
* @param rate - poll rate interval in seconds
*/
inline size_t PollInterval(){return (tPoller.getInterval() / 1000);};
size_t PollInterval(uint16_t rate);
inline mcstate_t collector(){return mcstate;};
/**
* @brief - change collector state to DISABLE/RUN/PAUSE
* @param newstate - new state
*/
mcstate_t collector(const mcstate_t newstate);
/**
* @brief - resize memory pool for metrics storage
* @param size - desired new size
* if new size is less than old - mem pool is shrinked, releasing memory (index pointer might change)
* if the new size is more than old one - full reallocation is done, all current sampling data is lost!
*/
size_t poolResize(size_t size);
uint16_t getMetricsIdx(){return poolidx;}
const std::vector<pmeterData> *getData() const { return samples; }
void setPollCallback(callback_function_t callback){
callback ? _cb_poll = std::move(callback) : _cb_poll = nullptr;
};
private:
// meter object
std::shared_ptr<PMETER> meter;
std::vector<pmeterData> *samples = nullptr;
// callback pointers
callback_function_t _cb_poll = nullptr;
mcstate_t mcstate = mcstate_t::MC_DISABLE; // Metrics collector state
uint16_t poolidx; // index pointer to next element in a pool
size_t poolsize; // desired mem pool size
Task tPoller;
// poll meter for data and store sample in RAM ring buffer (if required)
void datapoller();
size_t poolAlloc(size_t size);
};
class ESPEM {
public:
/**
* Class constructor
* uses predefined values of a ESPEM_CFG
*/
ESPEM(){}
/**
* Class constructor
* initialized with customized ESPEM_CFG
*/
ESPEM(const ESPEM_CFG& _cfg) : ecfg(_cfg){}
~ESPEM(){}
bool begin();
/** @brief onNetIfUp - коллбек для внешнего события "сеть доступна"
*
*/
void onNetIfUp();
/** @brief onNetIfDown - коллбек для внешнего события "сеть НЕ доступна"
*
*/
void onNetIfDown();
static float pfcalc(const float result[]);
/**
* @brief - HTTP request callback with latest polled data (as json)
*
*/
void wdatareply(AsyncWebServerRequest *request);
void wpmdata(AsyncWebServerRequest *request);
void wsamples(AsyncWebServerRequest *request);
/**
* @brief - set meter poll rate in seconds
* @param seconds - poll interval rate
*/
size_t PollRate(uint16_t seconds){
if (metrics){
return metrics->PollInterval(seconds);
} else { return 0;};
};
size_t PollRate(){
if (metrics){
return metrics->PollInterval();
} else { return 0;};
};
/**
* @brief - Control meter polling
* @param active - enable/disable
* @return - current state
*/
bool meterPolling(bool active){
if (metrics){
return metrics->polling(active);
} else { return false;};
};
bool meterPolling(){
if (metrics){
return metrics->polling();
} else { return false;};
};
bool pffix(){
if (meter){
return meter->pffix;
} else
return false;
}
bool pffix(const bool fix){
if (meter)
meter->pffix=fix;
return pffix();
}
/**
* @brief - get metrics storage capacity if any
*
*/
size_t getMetricsCap(){
if (metrics && metrics->getData())
return metrics->getData()->capacity();
return 0;
}
size_t poolResize(size_t size){
return (metrics ? metrics->poolResize(size) : 0);
};
mcstate_t collector(){
return (metrics ? metrics->collector() : mcstate_t::MC_DISABLE);
}
mcstate_t collector(mcstate_t state){
// if (state > (uint8_t)mcstate_t::MC_PAUSE)
// state = (uint8_t)mcstate_t::MC_RUN;
return (metrics ? metrics->collector((mcstate_t)state) : mcstate_t::MC_DISABLE);
}
protected:
private:
ESPEM_CFG ecfg;
std::shared_ptr<PMETER> meter = nullptr; // PZEM object
std::unique_ptr<PMETRICS> metrics = nullptr; // Metrics poller/collector
String& mktxtdata ( String& txtdata);
// make json string out of array provided
// bool W - include energy counter in json
// todo: provide vector with flags for each field
String& mkjsondata( const float result[], const unsigned long tstamp, String& jsn, const bool W );
void wspublish();
};