/
RangeTest_Transmitter.ino
259 lines (234 loc) · 10.4 KB
/
RangeTest_Transmitter.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
//*********************************************************************************************
// RangeTest_Transmitter (to be used with a MotionMote or similar Moteino)
// Adapted from MotionMote firmware sketch
// Acts as a continuous transmitter for testing RF performance, in tandem with a RangeTest_Gateway
//*********************************************************************************************
// Copyright Felix Rusu 2021, http://www.LowPowerLab.com/contact
//*********************************************************************************************
#include <RFM69.h> //https://www.github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h>//https://www.github.com/lowpowerlab/rfm69
#include <LowPower.h> //https://github.com/lowpowerlab/lowpower
#include <SPIFlash.h> //https://www.github.com/lowpowerlab/spiflash
//*********************************************************************************************
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************
//*********************************************************************************************
#define NODEID 2 //unique for each node on same network
#define NETWORKID 100 //the same on all nodes that talk to each other
#define GATEWAYID 1
#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ, match to radio variant on your Moteino/board
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
#define IS_RFM69HW_HCW //assumes RFM69 HCW/HW, remove if you have RFM69 W/CW
#define ENABLE_ATC //comment out to disable AUTO TRANSMISSION CONTROL (ie. always transmit at max power)
#define ATC_RSSI -90 //noise floor is at -100dB, keep above by a margin of 10db
//*********************************************************************************************
//************ GPIO & MISC ********************************************************************
//*********************************************************************************************
#define ACK_TIME 30 // max # of ms to wait for an ack
#define ONBOARDLED 9 // MotionMote onboard LED on D9
#define PIR_POWER 7 //PIR is powered from D7
#define RFM_RST A0 //used to reset the radio module at power up to ensure a clean start
#define MOTION_PIN 3 // D3
#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION
#define LED_PWR 6 // separate MotionMote LED powered from D6
#define LED_GND 5 // separate MotionMote LED ground on D5
#define LED_HIGH digitalWrite(LED_PWR, HIGH)
#define LED_LOW digitalWrite(LED_PWR, LOW)
#define DUPLICATE_INTERVAL 10000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox)
#define BATT_INTERVAL 4000 // read and report battery voltage every this many ms (approx)
#define INTERNAL_AREF_V 1100 //=1.1v internal bandgap. can be adjusted to more or less to be more accurate
//*********************************************************************************************
#define DEBUG_EN //comment this out when deploying to an installed SM to save a few KB of sketch size
#ifdef DEBUG_EN
#define SERIAL_BAUD 500000
#define DEBUG(input) Serial.print(input)
#define DEBUGln(input) Serial.println(input)
#define DEBUGHEX(input, param) Serial.print(input, param)
#define DEBUGFlush() Serial.flush()
#else
#define DEBUG(input)
#define DEBUGln(input)
#define DEBUGHEX(input, param)
#define DEBUGFlush();
#endif
//*********************************************************************************************
#ifdef ENABLE_ATC
RFM69_ATC radio;
#else
RFM69 radio;
#endif
SPIFlash flash(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
volatile boolean motionDetected=false;
float batteryVolts = 5;
float temp = 0;
char BATstr[10]; //longest battery voltage reading message = 9chars
char TEMPstr[10];
char sendBuf[32];
void motionIRQ(void);
void checkBattery(void);
void setup() {
#ifdef DEBUG_EN
Serial.begin(SERIAL_BAUD);
#endif
if (flash.initialize()) {
DEBUGln(F("SPI Flash Init OK!"));
flash.sleep(); //safe to call because initialize() wakes it up
}
else DEBUGln(F("SPI Flash MEM FAIL!"));
pinMode(MOTION_PIN, INPUT);
pinMode(ONBOARDLED, OUTPUT);
pinMode(PIR_POWER, OUTPUT);
pinMode(RFM_RST, OUTPUT);
pinMode(LED_PWR, OUTPUT);
pinMode(LED_GND, OUTPUT);
digitalWrite(PIR_POWER, HIGH);
digitalWrite(RFM_RST, LOW);
attachInterrupt(MOTION_IRQ, motionIRQ, RISING);
digitalWrite(RFM_RST, HIGH); delay(100); digitalWrite(RFM_RST, LOW);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
#ifdef IS_RFM69HW_HCW
radio.setHighPower();
#endif
radio.encrypt(ENCRYPTKEY);
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
#ifdef ENABLE_ATC
radio.enableAutoPower(ATC_RSSI);
DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n");
#endif
DEBUG("Transmitting at "); DEBUG(radio.getFrequency()); DEBUGln(" Hz..");
radio.sendWithRetry(GATEWAYID, "START", 5);
}
void motionIRQ() {
motionDetected=true;
DEBUGln("IRQ");
}
uint32_t time=0, now=0, MLO=0, BLO=0;
byte motionRecentlyCycles=0;
byte ackCount=0;
byte packetCounter=0;
void loop() {
now = millis();
checkBattery();
//DEBUG("Slept: ");DEBUG(now-lastSleepTime);DEBUGln("ms");
if (motionDetected && (time-MLO > DUPLICATE_INTERVAL)) {
packetCounter++;
LED_HIGH; //digitalWrite(LED, HIGH);
MLO = BLO = time; //save timestamp of event
sprintf(sendBuf, "%d MOTION X:%d", packetCounter, radio.getPowerLevel());
DEBUG(sendBuf);
if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) {
DEBUG("..OK! RSSI:");
DEBUG(radio.RSSI);
listen_a_little();
} else DEBUG("..NOK..");
radio.sleep();
LED_LOW;
} else if (time-BLO > BATT_INTERVAL) {
packetCounter++;
sprintf(sendBuf, "%d V:%s F:%s X:%d", packetCounter, BATstr, TEMPstr, radio.getPowerLevel());
BLO = time;
DEBUG(sendBuf);
if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) {
DEBUG("..OK!");
listen_a_little();
} else DEBUG("..NOK..");
}
DEBUGln(); DEBUGFlush();
//while motion recently happened sleep for small slots of time to better approximate last motion event
//this helps with debouncing a "MOTION" event more accurately for sensors that fire the IRQ very rapidly (ie panasonic sensors)
if (motionDetected || motionRecentlyCycles>0) {
if (motionDetected) motionRecentlyCycles=8;
else motionRecentlyCycles--;
motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected
time = time + 250 + millis()-now; //correct millis() resonator drift, may need to be tweaked to be accurate
radio.sleep();
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
DEBUGln("WAKEUP250ms");
} else {
time = time + 4000 + millis()-now /*+ 480*/; //correct millis() resonator drift, may need to be tweaked to be accurate
radio.sleep();
LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); //watchdog sleep uses extra ~4uA!
//sleep(4000);
DEBUGln("WAKEUP4s");
}
}
uint32_t BLR=0;
void checkBattery()
{
if (time-BLR > 3) //only read battery every 30s or so
{
BLR = time;
long vavg = 0;
temp = 0;
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // Ref to Vcc. Measure internal 1.1V ref
for (int j = 0; j < 10; j++)
{ // Read a few times to get ADC to settle
ADCSRA |= _BV(ADSC); // Start conversion
temp += radio.readTemperature(-1); // Temperature. -1 = user cal factor, adjust for correct ambient
while (bit_is_set(ADCSRA,ADSC)); // measuring
if (j > 4) { // Skip the first 5 Vcc readings, take the next 5
vavg = vavg + (((INTERNAL_AREF_V * 1024L) / ADC) + 5L);
}
}
batteryVolts = (vavg/5.0)/1000.0;
temp /= 10;
dtostrf(temp, 2,0, TEMPstr);
dtostrf(batteryVolts, 3,1, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
}
}
void sleep(uint32_t sleepTime) {
DEBUGFlush();
if (sleepTime < 262) { //sleeps just the MCU, using WDT (radio is not touched)
LowPower.longPowerDown(sleepTime);
} else { //sleeps MCU using the radio timer - should not be used if radio needs to be in RX mode!
uint32_t freq = radio.getFrequency();
uint32_t remainingSleepTime = sleepTime;
while (remainingSleepTime) { //split into sleep(60s) calls if > 60s sleep
if (remainingSleepTime > 65500) {
sleepTime = 65500;
remainingSleepTime -= 65500;
} else {
sleepTime = remainingSleepTime;
remainingSleepTime = 0;
}
if (sleepTime%262 && sleepTime > 262*2) {
DEBUG("Sleep "); DEBUGln(sleepTime-sleepTime%262-262); DEBUGFlush();
listenModeSleep(sleepTime-sleepTime%262-262);
DEBUG("Sleep "); DEBUGln(sleepTime%262 + 262); DEBUGFlush();
listenModeSleep(sleepTime%262 + 262);
} else {
DEBUG("Sleep "); DEBUGln(sleepTime); DEBUGFlush();
listenModeSleep(sleepTime);
}
//WAKEUP happens here (must reinit!)
radio.RFM69::initialize(FREQUENCY,NODEID,NETWORKID); //call base init!
#ifdef ENCRYPTKEY
radio.encrypt(ENCRYPTKEY);
#endif
radio.setFrequency(freq);
}
}
}
void listenModeSleep(uint16_t millisInterval) {
radio.listenModeSleep(millisInterval);
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
radio.endListenModeSleep();
}
void listen_a_little() {
radio.receiveDone(); //radio RX!
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
if (radio.receiveDone()) {
DEBUG(" [");DEBUG(radio.SENDERID);DEBUG("] ");
DEBUG((char*)radio.DATA);
DEBUG("[RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
if (radio.ACKRequested()) {
radio.sendACK();
DEBUG("[ACK sent]");
}
time += 10;
} else time += 60;
}