-
Notifications
You must be signed in to change notification settings - Fork 2
/
efergy.cpp
489 lines (452 loc) · 16.6 KB
/
efergy.cpp
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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
//Based on code from:
//http://rtlsdr-dongle.blogspot.com.au/2013/11/finally-complete-working-prototype-of.html
//http://electrohome.pbworks.com/w/page/34379858/Efergy%20Elite%20Wireless%20Meter%20Hack
//DATA Out from Receiver in pin D3 (remember 3.3v!!)
#include "Arduino.h"
#include <limits.h> //Required for our version of PulseIn
#include "wiring_private.h" //Required for our version of PulseIn
#include "pins_arduino.h" //Required for our version of PulseIn
#include "efergy.h"
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
// Used for Accurate Clock cycle timing for receiving the bits from the Efergy Receiver
#define RSR_CCOUNT(r) __asm__ __volatile__("rsr %0,ccount":"=a" (r))
#define EFERGY_PINWAIT(state) \
while (digitalRead(pin) != (state)) { if (get_ccount() - start_cycle_count > timeout_cycles) { return 0; } }
int _rxpin;
int _debug;
bool _startcom = false; //True when a packet is in the process of being received
unsigned long _processingtime; //needs to be unsigned long to be compatible with PulseIn()
unsigned long _incomingtime[limit]; //stores processing time for eac
int _bytecount;
efergy::efergy(int inputpin, int debug, int volts) {
_rxpin = inputpin;
_debug = debug;
_voltage = volts;
}
void efergy::begin(int baudrate) {
pinMode(_rxpin, INPUT);
Serial.begin(baudrate); // Setup Serial Port to allow for logging -74880 is for esp8266 compatibility
milliswait(1500); // This ensures our serial has had time to get ready
if (_debug) {
eflog("DEBUG IS ON - T=Timeout, S=Start, b=bit, o=Rxtimeout, L=loop routine, E=End of Packet", true);
}
eflog("Efergy Monitor has intitialized.",true);
}
// Routing to Output log messages, nl = new line 1(default=true) 0=append to existing line
void efergy::eflog(const char* LOGMSG, bool nl) {
char logbuff[(strlen(LOGMSG) + 20)];
if (nl) {
sprintf(logbuff, "\n[%s] %s", timesinceboot(), LOGMSG);
} else {
sprintf(logbuff, "%s", LOGMSG);
}
Serial.print(logbuff);
}
// Routing to Output log messages, nl = new line 1(default=true) 0=append to existing line
void efergy::eflog(String LOGMSG, bool nl) {
char Buf[70];
LOGMSG.toCharArray(Buf, 70);
eflog(Buf,nl);
}
// Wait a while - but loop in a yield to make our time useful
void efergy::milliswait(unsigned long wait_ms) {
unsigned long future = millis() + wait_ms;
while (future >= millis()) {
yield();
}
}
// Obtain time since boot in 00d 00:00.00 format
char * efergy::timesinceboot() {
unsigned long runMillis = millis();
unsigned long allSeconds = runMillis / 1000;
int runDays = allSeconds / 86400;
int secsRemaining = allSeconds % 86400;
int runHours = allSeconds / 3600;
secsRemaining = allSeconds % 3600;
int runMinutes = secsRemaining / 60;
int runSeconds = secsRemaining % 60;
static char timebuf[13];
sprintf(timebuf, "%02dd %02d:%02d.%02ds", runDays, runHours, runMinutes, runSeconds);
return timebuf;
}
// Make 16-bit Transmitter ID from received by array
unsigned long efergy::RXdecodeID(unsigned char _bytearray[8]) {
return (((unsigned int)_bytearray[1] * 256) + (unsigned int)_bytearray[2]);
}
// Power of a number - normal Arduino function uses float and 2KB of Flash - this is much smaller
// Used the milliamp decode reoutine
unsigned long efergy::power2 (unsigned char exp1) {
unsigned long pow1 = 1048576;
exp1 = exp1 + 5;
for (int x = exp1; x > 0; x--) {
pow1 = pow1 / 2;
}
return pow1;
}
// Decode from the RX packet what the milliAmp current draw is
bool efergy::RXdecodeMA_valid(unsigned char _bytearray[8]) {
unsigned long rxma = ((1000 * ((unsigned long)((_bytearray[4] * 256) + (unsigned long)_bytearray[5]))) / power2(_bytearray[6]));
if ( rxma > 100000 ) {
return false; // Return Null if value is invalid/out of range (TEST THIS)
} else {
return true; // Return mA (milliamps) of load
}
}
// Decode from the RX packet what the milliAmp current draw is
unsigned long efergy::RXdecodeMA(unsigned char _bytearray[8]) {
unsigned long rxma = ((1000 * ((unsigned long)((_bytearray[4] * 256) + (unsigned long)_bytearray[5]))) / power2(_bytearray[6]));
if ( rxma > 100000 ) {
return 0; // Return Null if value is invalid/out of range (TEST THIS)
} else {
return rxma; // Return mA (milliamps) of load
}
}
// Decode from the RX pcket what the Wattage is (using a custom voltage provided)
unsigned long efergy::RXdecodeW(unsigned char _bytearray[8], int volts) {
unsigned long rxma = RXdecodeMA(_bytearray);
unsigned long rx_watts = 0;
if (rxma) {
unsigned long rx_watts = ( rxma * volts ) / 1000;
}
return rx_watts;
}
// Decode the Interval between refreshes of the data from the transmitter
int efergy::RXdecodeI(unsigned char _bytearray[8]) {
//Transmit Intervals
if ( (_bytearray[3] & 0x30) == 0x10) { //xx11xxxx = 12 seconds
return 12;
} else if ( (_bytearray[3] & 0x30) == 0x20) { //xx10xxxx = 18 seconds
return 18;
} else if ( (_bytearray[3] & 0x30) == 0x00) { //xx00xxxx = 6 seconds
return 6;
} else {
return 0;
}
}
// Decode from RX packet if the Link marker is set. Transmitter is in pairing mode
bool efergy::RXdecodeP(unsigned char _bytearray[8]) {
if ( (_bytearray[3] & 0x80) == 0x80) {
return true;
} else if ( (_bytearray[3] & 0x80) == 0x00 ) {
return false;
}
return false;
}
// Decode the Battery Status from the 8 byte received array
bool efergy::RXdecodeB(unsigned char _bytearray[8]) {
//Check the Battery status of the Transmitter - False means low battery
if ( (_bytearray[3] & 0x40) == 0x40) {
return true; // true=ok
} else {
return false; // false=bad
}
return false;
}
// Decode the Checksum from the received packet
bool efergy::RXdecodeCS(unsigned char CSbytes[]) {
unsigned char tbyte = 0;
bool OK1 = false;
for (int cs = 0; cs < 7; cs++) {
tbyte += CSbytes[cs];
}
tbyte &= 0xff;
if (_debug > 3) {
Serial.print("CheckSum Calc=");
Serial.print(tbyte);
Serial.print(" RX Checksum=");
Serial.println(CSbytes[7]);
}
if ( tbyte == CSbytes[7] ) {
if ( CSbytes[0] == 7 || CSbytes[0] == 9 ) {
OK1 = true;
}
}
return OK1;
}
// unsigned long _incomingtime[limit]
void efergy::RXdecodeRAW(unsigned long _incomingtime[],unsigned char * _bytearray) {
int dbit = 0;
int bitpos = 0;
_bytecount = 0;
unsigned char bytedata = 0;
for (int k = 1; k < limit; k++) { //Start at 1 because the first bit (0) is our long 500uS start
if (_incomingtime[k] != 0) {
if (_incomingtime[k] > 10UL ) { //Original Code was 20 - smallest is about 70us - so 40 to be safe with loop overheads
dbit++;
bitpos++;
bytedata = bytedata << 1;
if (_incomingtime[k] > 100UL) { // 0 is approx 70uS, 1 is approx 140uS
bytedata = bytedata | 0x1;
}
if (bitpos > 7) {
_bytearray[_bytecount] = bytedata;
bytedata = 0;
bitpos = 0;
_bytecount++;
}
}
}
}
}
// Perform reset ready for new packet to be received
void efergy::RESET_PKT() {
_startcom = false;
//memset (_incomingtime, -1, sizeof(_incomingtime));
memset (_incomingtime, 0, sizeof(_incomingtime));
memset (_bytearray, 0, sizeof(_bytearray));
}
void efergy::Serial_BitTimes(int z) {
Serial.print("{\"BituSec\":[");
for (int y = 0; y <= z; y++) {
Serial.print(_incomingtime[y]);
if (y < z ) {
Serial.print(",");
} else {
Serial.println("]}");
}
}
}
// Dump out the raw byte array to the Serial port (used for debugging)
void efergy::Serial_RAW(unsigned char Rbytes[]) {
// if (!RXdecodeCS(Rbytes)) {
if (true) {
char buf[64];
sprintf(buf, "{\"RAW\":[%d,%d,%d,%d,%d,%d,%d,%d]}", Rbytes[0], Rbytes[1], Rbytes[2], Rbytes[3], Rbytes[4], Rbytes[5], Rbytes[6], Rbytes[7]);
Serial.print(buf);
}
}
//static inline uint32_t efergy::get_ccount(void) {
uint32_t efergy::get_ccount(void) {
uint32_t ccount;
RSR_CCOUNT(ccount);
return ccount;
}
// max timeout is 27 seconds at 160MHz clock and 54 seconds at 80MHz clock
unsigned long efergy::Efergy_pulseIn(uint8_t pin, uint8_t state, unsigned long timeout) {
if (timeout > 1000000) {
timeout = 1000000;
}
const uint32_t timeout_cycles = microsecondsToClockCycles(timeout);
const uint32_t start_cycle_count = get_ccount();
EFERGY_PINWAIT(!state);
EFERGY_PINWAIT(state);
const uint32_t pulse_start_cycle_count = get_ccount();
EFERGY_PINWAIT(!state);
return clockCyclesToMicroseconds(get_ccount() - pulse_start_cycle_count);
}
bool efergy::mainloop() {
//Loop through and store the high pulse length
int flag = false;
int p = 0;
int bytecount;
char tempbuff[60]; //Temporary buffers for charactor concatenating, etc...
String bitRXdebug = "";
_processingtime = Efergy_pulseIn(_rxpin, HIGH, 5000); //Returns unsigned long - 5 millisecond timeout
if ( _processingtime > 450UL && _processingtime < 580UL ) {
//If the High Pulse is greater than 450uS - this is the start of a packet
_startcom = true;
_incomingtime[0] = _processingtime;
p = 1;
if (_debug > 2) { bitRXdebug = "Sb"; }
//Process individual bits in this loop - store array of packet times in incomingtime[]
while ( _startcom ) {
_processingtime = Efergy_pulseIn(_rxpin, HIGH, 600); //Returns unsigned long - 300uS timeout
if ( _processingtime == 0 ) { //With An Active Signal we will basically never timeout
if (_debug > 2) { bitRXdebug += "T";}
RESET_PKT();
} else if ( _processingtime > 450UL && _processingtime < 580UL ) {
//Start of new packet - reset if part of the way through - helps with interference
_incomingtime[0] = _processingtime;
p = 1;
} else if (_processingtime > 10 ) {
_incomingtime[p] = _processingtime; //Save time of each subsequent bit received
p++;
if (_debug > 2) { bitRXdebug += "b"; }
//If packet has been received (67 bits) - mark it to be processed
if (p > limit) {
// If our length is longer than our buffer - then reset
_startcom = false;
yield(); // Yield ASAP as we are receiving a mess of information - give some time back
flag = true; // Flag set for complete packet received
if (_debug > 2) { bitRXdebug += "E"; }
}
//end of limit if
} else {
if (_debug > 2) { bitRXdebug += "X";}
}
//end of proctime > 30
}
//End of _startcom == true
} else if ( _processingtime == 0 ) { //End of packet RX
yield();
}
//If a complete packet has been receied (flag = 1) then process the individual bits into a byte array
if ( flag == true ) {
RXdecodeRAW(_incomingtime,_bytearray);
//Process Received Packet - 8 bytes long - with a valid checksum
if ( _bytecount == 8 && RXdecodeCS(_bytearray)) {
flag = false;
int TXID = RXdecodeID(_bytearray);
int TXbatt = RXdecodeB(_bytearray);
_eventjson.clear();
_eventjson["ts"] = (millis() / 100);
_eventjson["id"] = TXID;
_eventjson["type"] = "RX";
if (RXdecodeMA_valid(_bytearray)) {
_eventjson["mA"] = RXdecodeMA(_bytearray);
_eventjson["W"] = RXdecodeW(_bytearray, _voltage);
} else {
_eventjson["mA"] = 'invalid';
_eventjson["W"] = 'invalid';
}
_eventjson["Int"] = RXdecodeI(_bytearray);
_eventjson["Pair"] = (RXdecodeP(_bytearray) ? "On" : "Off");
_eventjson["Batt"] = (TXbatt ? "OK" : "Low");
_eventjson["Mon"] = (getMonitoredTX(TXID) ? "Yes" : "No");
//Log our Received Data
eflog(getcharevent(),true);
// TODO: check if mA is valid
if (getMonitoredTX(TXID)) {
// This transmitter we are logging
if ( eventID(TXID,_eventjson["mA"],TXbatt) ){ return true; }
}
} else {
//We failed the Checksum test on an 8 byte Packet
if (_debug) {
eflog("Received Data failed Checksum - or incomplete packet",true);
Serial_BitTimes(limit);
}
}
if (_debug) {
if (_debug > 2 ) {
Serial.print("\nBit Pattern=");
Serial.println(bitRXdebug);
}
if (_debug > 4 ) { Serial_BitTimes(limit); }
Serial_RAW(_bytearray);
}
flag = false;
RESET_PKT();
}
return false;
// End of FLAG == true Routine
}
StaticJsonDocument<256> efergy::getjsonevent() {
return _eventjson;
}
char * efergy::getcharevent() {
static char jsonstr[128];
serializeJson(_eventjson, jsonstr);
return jsonstr;
}
// Whitelist TX
// Interval for updates (mAmin,mAavg,mAmax,mAnow, lost packets, battery, jitter?, operating (on/off))
bool efergy::eventID(int id, int valmA, bool battery) {
//get Listref of TXID
int arrayid = getdetailTXid(id);
bool reporttime = false;
int val_size = _IDinfo["log"][arrayid]["val"].size();
//update last RX timestamp on TX
_IDinfo["log"][arrayid]["lastRX"] = millis() / 1000; //Store time in seconds since last Rx
// ADD new value to end of buffer
_IDinfo["log"][arrayid]["val"].add(valmA);
//remove old value if over buffer depth
if (val_size >= int(_IDinfo["log"][arrayid]["dpth"]) ) {
_IDinfo["log"][arrayid]["val"].remove(0);
}
//If our Json document is getting full - perform a clear to defrag it
IDinfo_monitor();
//Report at Interval
int val_interval = int(_IDinfo["log"][arrayid]["int"]);
if ( ( int(_IDinfo["log"][arrayid]["lastrep"]) + val_interval ) <= int(millis() / 1000) ) {
_IDinfo["log"][arrayid]["lastrep"] = int(millis() / 1000); //Store time in seconds since last report created
// array of values to loop through
int val_min = 9999999;
int val_max = 0;
long val_total = 0;
for (int i = 0; i < _IDinfo["log"][arrayid]["val"].size(); i++) {
int arrval = int(_IDinfo["log"][arrayid]["val"][i]);
//Serial.println(arrval);
if (arrval < val_min) { val_min = arrval; }
if (arrval > val_max) { val_max = arrval; }
val_total = val_total + arrval;
}
int val_avg = int(val_total / ( _IDinfo["log"][arrayid]["val"].size() ) );
_eventjson.clear();
_eventjson["ts"] = (millis() / 100);
_eventjson["id"] = id;
_eventjson["type"] = "report";
_eventjson["avg"] = val_avg;
_eventjson["min"] = val_min;
_eventjson["max"] = val_max;
_eventjson["batt"] = (battery ? "OK" : "Low");
if ( val_avg < int(_IDinfo["log"][arrayid]["sw"]) ) {
_eventjson["status"] = false;
} else {
_eventjson["status"] = true;
}
if ( _IDinfo["log"][arrayid]["status"] != _eventjson["status"] ) {
_eventjson["changed"] = true;
} else {
_eventjson["changed"] = false;
}
_IDinfo["log"][arrayid]["status"] = _eventjson["status"];
eflog(getcharevent(),true);
// Ensure we hide the first report - prevent lots of change alerts going off on bootup
if ( _IDinfo["log"][arrayid]["init"] == true ) { reporttime = true; }
_IDinfo["log"][arrayid]["init"] = true;
}
return reporttime;
}
//Setup parameters for a transmitter - interval can be =<15 (minutes) = 150 rx packets
void efergy::setID(int id, int depth, unsigned long statusmA, int intervalsecs) {
StaticJsonDocument<512> newTX;
_IDinfo["TX"].add(id);
newTX["id"] = id; // The ID number of the Transmitter
newTX["dpth"] = depth; // The depth of value buffer we will keep for min/max/avg stats
newTX["sw"] = statusmA; // mA to switch the status from off/on
newTX["int"] = intervalsecs; // how many seconds between reporting min/avg/max/status
_IDinfo["log"].add(newTX);
char tempbuff[90]; //Temporary buffers for charactor concatenating, etc...
sprintf(tempbuff,"ADDED TX %d with %d events logged, status will change@ %dmA, report@ %d seconds",id,depth,statusmA,intervalsecs);
eflog(tempbuff,true);
//PrintJSON_IDinfo();
}
// Get if the TX is a monitored TX to log values to report on
bool efergy::getMonitoredTX(int id) {
for (int i = 0; i < _IDinfo["TX"].size(); i++) {
if ( int(_IDinfo["TX"][i]) == id ) {
return true;
}
}
return false;
}
// Get if the TX is a monitored TX to log values to report on
int efergy::getdetailTXid(int id) {
for (int i = 0; i < _IDinfo["log"].size(); i++) {
if ( int(_IDinfo["log"][i]["id"]) == id ) {
return i;
//TX is in our list
}
}
return 0;
}
// Print out the contents of the IDinfo Json document for logging
void efergy::PrintJSON_IDinfo() {
serializeJsonPretty(_IDinfo, _printme);
Serial.println(_printme);
}
//Check the Idinfo Json document - ensure it isn't full, and perform some cleaning
void efergy::IDinfo_monitor() {
if ( int(_IDinfo.memoryUsage()) > int( BUFFERSIZE * .9) ) {
eflog("[INFO] Performing Garbage Collection",true);
_IDinfo.garbageCollect(); // Defragment JSON buffer
if ( int(_IDinfo.memoryUsage()) > int( BUFFERSIZE * .8) ) {
eflog("[WARN] Buffer size - reduce depth, increase Tx Interval.",true);
for (int arri = 0; arri < _IDinfo["log"].size(); arri++) {
_IDinfo["log"][arri]["val"].remove(0);
_IDinfo["log"][arri]["val"].remove(1);
}
}
}
}