-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmotenio_R5_RFM69HW_Pulse_RJS_Pool_Meter.ino
566 lines (412 loc) · 19.9 KB
/
motenio_R5_RFM69HW_Pulse_RJS_Pool_Meter.ino
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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
/*
** moteino_R5_RFM69HW_Pulse_RJS_Pool_Meter
** Node 28
** 433Mhz
RJS PulseMeter V2.0 Moteino 9/25/2017
This sketch is designed to read pulses from a water meter using a low-power, battery operated Moteino R4 (http://www.moteino.com).
This sketch is derived from different sketches from both moteino.com and openenergymonitor.org. Many thanks to all who have helped
in this process. Parts of this sketch are derived from a sketch that Felix at LowPowerLabs wrote called PulseMeter. It has been
modified to work for my situation.
While the Moteino can transmit information to it's own gateway, I am using a RaspberryPi (emonPi) created and maintained by the folks
at Open Energy Monitoring. I then take this information and write it to a privately hosted version of emoncms, although you could also
use the free emoncms hoting provided by http://emoncms.org
While every effort has been made to keep the power consumption as low as possible, I am sure it could be better, so this should be
considered a starting point!
The particular meters I am using output 10 pulses per gallon using a reed style switch.
I was having a bunch of problems debouncing in software as my water meters would kick out upwards of 300 pulses per minute. I tried a variety
of options but was never quite able to get it right, so I spent $2.00 on a Max6816 digital debouncer (thanks MikeSims). It works like magic!
In addition, the Moteinos that I am using have onboard flash, so I write the pulsecount every minute to the flash, this way I never lose (or reset)
my overall pulsecount. In addition, you can wirelessly program the Moteinos which is very nice.
How to connect a two wire reed meter to your Moteino (if you are not using a physical debouncer)
.......................
1) Connect one of the leads of the meter to 3.3v
2) Connect the other lead to a 10k ohm resistor.
3) Connect the same end of this resistor to pin D3 (INT1).
4) Connect the other end of the resistor to ground.
How to connect a two wire reed meter to your Moteino (if you are using the Max6816 physical debouncer)
.......................
1) Connect VCC on Max6816 to 3.3v.
2) Connect Ground on Max6816 to ground.
3) Connect a .1uF cap between VCC and Ground on Max 6816. (May not be needed...I added it anyway).
4) Connect one lead from water meter to IN on Max6816.
5) Connect the other lead from the meter to ground.
6) Finally connect the OUT on the MAX6816 to D3.
Again, this is designed to talk directly to an emonpi not to a moteino gateway, so if you want to talk to a gateway,
you will need to make modifications to this sketch.
This is what my emonhub.conf looks like:
[[28]]
nodename = pool_water_meter
firmware = moteino_R5_RFM69HW_Pulse_RJS_Pool_Meter
hardware = moteino_R5
[[[rx]]]
names = GAL, GPM, GLM
datacodes = f,f,f
scales = 1,1,1
units = G,G,G
Recommended node ID allocation
-----------------------------------------------------------------------------------------------------------
-ID- -Node Type-
0 - Special allocation in JeeLib RFM12 driver - reserved for OOK use
1-4 - Control nodes
5-10 - Energy monitoring nodes
11-14 --Un-assigned --
15-16 - Base Station & logging nodes
17-30 - Environmental sensing nodes (temperature humidity etc.)
31 - Special allocation in JeeLib RFM12 driver - Node31 can communicate with nodes on any network group
-------------------------------------------------------------------------------------------------------------
*/
#define debug 1 // Set to 1 to few debug serial output, turning debug off increases battery life
#define serial_speed 9600
#define RF69_COMPAT 1 // Set to 1 if using RFM69CW or 0 is using RFM12B
//*********************************************************************************************
//*********** Include these libraries:
//*********************************************************************************************
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
#include <EEPROM.h>
#include <SPI.h>
#include <TimerOne.h>
#include <JeeLib.h>
#include <avr/power.h>
#include <avr/sleep.h>
//*********************************************************************************************
ISR(WDT_vect) {
Sleepy::watchdogEvent();
} // Attached JeeLib sleep function to Atmega328 watchdog - enables MCU to be put into sleep mode between readings to reduce power consumption
//*********************************************************************************************
//*********************************************************************************************
//*********** Here is where we setup our flash if we have it.
//*********************************************************************************************
#ifdef __AVR_ATmega1284P__
#define LED 15 // Moteino MEGAs have LEDs on D15
#define INTERRUPTPIN 1 //INT1 = digital pin 11 (must be a hardware interrupt pin!)
#define FLASH_SS 23 // and FLASH SS on D23
#else
#define LED 9 // Moteinos have LEDs on D9
#define INTERRUPTPIN 1 //INT1 = digital pin 3 (must be a hardware interrupt pin!)
#define FLASH_SS 8 // and FLASH SS on D8
#endif
#define wireless_programming 1 // Set to 1 to allow wireless programming, 0 to disable. If you enable, you must enable use_flash
#define use_flash 1 // Are we going to use the flash? If not, we can put it to sleep and save some power...
SPIFlash flash(FLASH_SS, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
//*********************************************************************************************
//*********************************************************************************************
//************ Here is all the radio stuff, change to meet your configuration!! **************
//*********************************************************************************************
RFM69 radio;
#define RF_freq RF12_433MHZ // Frequency of RF12B module can be RF12_433MHZ, RF12_868MHZ or RF12_915MHZ. You should use the one matching the module you have.
#define IS_HIGHPOWER true // True only for RFM69HW High Power RFM69
int nodeID = 28; // Node ID - should be unique on network - I use 30 for testing
const int networkGroup = 210; // Network group
//*********************************************************************************************
//*********************************************************************************************
//************ Here is all the pulse stuff, change to meet your configuration!! **************
//*********************************************************************************************
#define PULSESPERGALLON 10 //how many pulses from sensor equal 1 gallon
#define GPMTHRESHOLD 8000 // GPM will reset after this many MS if no pulses are registered
#define XMITPERIOD 5000 // GPMthreshold should be less than 2*XMITPERIOD
volatile byte ledState = LOW;
volatile unsigned long PulseCounterVolatile = 0; // use volatile for shared variables
unsigned long NOW = 0;
unsigned long PulseCounter = 0;
unsigned long LASTMINUTEMARK = 0;
unsigned long PULSECOUNTLASTMINUTEMARK = 0; //keeps pulse count at the last minute mark
byte COUNTEREEPROMSLOTS = 10;
unsigned long COUNTERADDRBASE = 8; //address in EEPROM that points to the first possible slot for a counter
unsigned long COUNTERADDR = 0; //address in EEPROM that points to the latest Counter in EEPROM
byte secondCounter = 0;
unsigned long TIMESTAMP_pulse_prev = 0;
unsigned long TIMESTAMP_pulse_curr = 0;
int pulseAVGInterval = 0;
int pulsesPerXMITperiod = 0;
//
int newgal = 67100;
//
float GALlast=0;
float GPMlast=0;
float GLMlast=0;
// What information are we going to be transmitting back to our gateway?
typedef struct { // RF payload datastructure
float GAL;
float GPM;
float GLM;
} Payload;
Payload water;
//################################################################################################################################
//################################################################################################################################
void setup() {
//################################################################################################################################
pinMode(LED,OUTPUT); digitalWrite(LED,HIGH); // Status LED on
// Because of the Moteino, we are using two different radio libraries to control the radio. rf_12 (jeelib) and radio (RFM69).
// We only use the radio() commands to control the power output and the sleeping. If you have the HW radio, I have found that
// you must use the radio.setHighPower() or the radio simply will not transmit. At some point I will dig through the libraries
// and adjust this so we do not need both libraries.
//*********************************************************************************************
//*********** Initialize our radio. I use the High Power version of the RFM69.
//*********************************************************************************************
rf12_initialize(nodeID, RF_freq, networkGroup);
if(IS_HIGHPOWER) radio.setHighPower(); // If we are using the RFM69HW version of the radio, set it to high power
rf12_sleep(RF12_SLEEP);
radio.sleep();
//*********************************************************************************************
// Since we are using a Moteino with onboard flash, we are able to save our meter readings to
// flash so we do not lose them in the event of a power outage or other problem. Here is where
// we initialize our counter from flash.
//*********************************************************************************************
unsigned long savedCounter = EEPROM_Read_Counter();
if (savedCounter <=0) savedCounter = 1; //avoid division by 0
PulseCounterVolatile = PulseCounter = PULSECOUNTLASTMINUTEMARK = savedCounter;
attachInterrupt(INTERRUPTPIN, pulseCounterInterrupt, RISING);
Timer1.initialize(XMITPERIOD * 1000L);
Timer1.attachInterrupt(XMIT);
//*********************************************************************************************
//*********** If we are not going to use flash, we can put it to sleep to save power.
//*********************************************************************************************
if (!use_flash)
{
flash_sleep();
}
//*********************************************************************************************
//*********** If debug mode is set above, output some useful information to the serial port.
//*********************************************************************************************
if (debug)
{
Serial.begin(serial_speed);
if (use_flash)
{
Serial.println("Checking for presence of onboard Flash...");
if (flash.initialize())
{
Serial.println("Onboard Flash Detected...attempting to initialize...");
dodelay(1000);
Serial.println("SPI Flash Init OK!");
Serial.println();
}
else
Serial.println("SPI Flash Init FAIL! (is chip present?)");
Serial.println();
}
else
Serial.println("Flash Not in Use, We are putting it to sleep to save power....");
dodelay(100);
Serial.println("moteino_RFM69HW_PULSE_Pool_Meter");
Serial.println();
#if (RF69_COMPAT)
Serial.println("RFM69HW Radio Initialized");
Serial.println();
#else
Serial.println("RFM12B Initialized");
Serial.println();
#endif
Serial.print("Node: ");
Serial.print(nodeID);
Serial.print(", Freq: ");
if (RF_freq == RF12_433MHZ) Serial.print("433Mhz");
if (RF_freq == RF12_868MHZ) Serial.print("868Mhz");
if (RF_freq == RF12_915MHZ) Serial.print("915Mhz");
Serial.print(", Network: ");
Serial.println(networkGroup);
Serial.println();
dodelay(1000);
}
//*********************************************************************************************
//*********** Shut off some other things we do not need to save power.
//*********************************************************************************************
reduce_power();
//*********************************************************************************************
//*********** All done with our setup.
//*********************************************************************************************
}
//################################################################################################################################
//################################################################################################################################
void loop()
//################################################################################################################################
{
//*********************************************************************************************
//*********** If you want to wireless program your Mote, set this to True above.
//*********************************************************************************************
if (use_flash && wireless_programming)
{
CheckForWirelessHEX(radio, flash, true);
}
}
//*********************************************************************************************
//**** Configure our interrupts, setup counters and timers and configure transmittions.
//*********************************************************************************************
void pulseCounterInterrupt()
{
noInterrupts();
ledState = !ledState;
PulseCounterVolatile++; // increase when LED turns on
digitalWrite(LED, ledState);
NOW = millis();
//remember how long between pulses (sliding window)
TIMESTAMP_pulse_prev = TIMESTAMP_pulse_curr;
TIMESTAMP_pulse_curr = NOW;
if (TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev > GPMTHRESHOLD)
//more than 'GPMthreshold' seconds passed since last pulse... resetting GPM
pulsesPerXMITperiod=pulseAVGInterval=0;
else
{
pulsesPerXMITperiod++;
pulseAVGInterval += TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev;
}
interrupts();
}
void XMIT()
{
noInterrupts();
PulseCounter = PulseCounterVolatile;
interrupts();
if (millis() - TIMESTAMP_pulse_curr >= 5000)
{
ledState = !ledState;
digitalWrite(LED, ledState);
}
//calculate Gallons counter
water.GAL = ((float)PulseCounter)/PULSESPERGALLON;
//calculate & output GPM
water.GPM = pulseAVGInterval > 0 ? 60.0 * 1000 * (1.0/PULSESPERGALLON)/(pulseAVGInterval/pulsesPerXMITperiod):0;
pulsesPerXMITperiod = 0;
pulseAVGInterval = 0;
secondCounter += XMITPERIOD/1000;
//*********************************************************************************************
//*********** Once per minute, put out a GallonsLastMinute count.
//*********************************************************************************************
if (secondCounter>=60)
{
if (debug)
{
Serial.println("60sec mark ... ");
}
secondCounter=0;
water.GLM = ((float)(PulseCounter - PULSECOUNTLASTMINUTEMARK))/PULSESPERGALLON;
PULSECOUNTLASTMINUTEMARK = PulseCounter;
EEPROM_Write_Counter(PulseCounter);
if (debug)
{
Serial.print("GAL: "); Serial.print(water.GAL);
Serial.print(", GPM: "); Serial.print(water.GPM);
Serial.print(", GLM: "); Serial.println(water.GLM);
}
}
else
{
if (debug)
{
Serial.print("GAL: "); Serial.print(water.GAL);
Serial.print(", GPM: "); Serial.println(water.GPM);
}
}
//*********************************************************************************************
//*********** We only transmit if there are changes to send...helps to save power....
//*********************************************************************************************
if (water.GPM!=GPMlast || water.GAL!=GALlast || water.GLM!=GLMlast) //Check and see if we used any water.....
{
power_spi_enable();
if (debug)
{
Serial.println("Time to transmit.......");
}
rf12_sleep(RF12_WAKEUP);
dodelay(100);
rf12_sendNow(0, &water, sizeof water);
rf12_sendWait(2);
//*********************************************************************************************
//*********** Reset our readings so we know when to transmit again...
//*********************************************************************************************
GALlast = water.GAL;
GPMlast = water.GPM;
GLMlast = water.GLM;
power_spi_disable();
}
}
//*********************************************************************************************
//*********** Flash/EEProm and powersaving Subroutines
//*********************************************************************************************
unsigned long EEPROM_Read_Counter()
{
return EEPROM_Read_ULong(EEPROM_Read_ULong(COUNTERADDR));
}
void EEPROM_Write_Counter(unsigned long counterNow)
{
if (counterNow == EEPROM_Read_Counter())
{
if (debug)
{
Serial.print("{EEPROM-SKIP(no changes)}");
}
return; //skip if nothing changed
}
if (debug)
{
Serial.print("{EEPROM-SAVE(");
Serial.print(EEPROM_Read_ULong(COUNTERADDR));
Serial.print(")=");
Serial.print(PulseCounter);
Serial.print("}");
}
unsigned long CounterAddr = EEPROM_Read_ULong(COUNTERADDR);
if (CounterAddr == COUNTERADDRBASE+8*(COUNTEREEPROMSLOTS-1))
CounterAddr = COUNTERADDRBASE;
else CounterAddr += 8;
EEPROM_Write_ULong(CounterAddr, counterNow);
EEPROM_Write_ULong(COUNTERADDR, CounterAddr);
}
unsigned long EEPROM_Read_ULong(int address)
{
unsigned long temp;
for (byte i=0; i<8; i++)
temp = (temp << 8) + EEPROM.read(address++);
return temp;
}
void EEPROM_Write_ULong(int address, unsigned long data)
{
for (byte i=0; i<8; i++)
{
EEPROM.write(address+7-i, data);
data = data >> 8;
}
}
void dodelay(unsigned int ms)
{
byte oldADCSRA=ADCSRA;
byte oldADCSRB=ADCSRB;
byte oldADMUX=ADMUX;
Sleepy::loseSomeTime(ms); // JeeLabs power save function: enter low power mode for x seconds (valid range 16-65000 ms)
ADCSRA=oldADCSRA; // restore ADC state
ADCSRB=oldADCSRB;
ADMUX=oldADMUX;
}
//******************************************************************************************************
// If we are not going to be using flash, there are some power savings things we can do. Here we make
// sure the flash pin is set to OUTPUT and HIGH so that it is not floating if we are not going to use it.
//******************************************************************************************************
void flash_sleep()
{
pinMode(FLASH_SS, OUTPUT); // Added to make sure flash stays asleep and does not use power!
digitalWrite(FLASH_SS, HIGH);
flash.sleep();
}
/**
* Turn off what we don't need.
* see http://www.nongnu.org/avr-libc/user-manual/group__avr__power.html
*/
void reduce_power()
{
// Shut off the LED for now....
digitalWrite(LED,LOW);
// Put the radio into sleep mode until we need it
rf12_sleep(RF12_SLEEP); // Put the radio to sleep using both libraries
radio.sleep();
ACSR |= (1 << ACD); // Disable Analog comparator
power_twi_disable(); // Disable the Two Wire Interface module.
power_spi_disable(); // Serial peripheral interface
if (!debug)
{
power_usart0_disable(); // Disable serial UART if not in debug
}
power_timer0_enable(); // Necessary for this sketch
power_timer1_enable(); // Necessary for this sketch
}