A year or so back I purchased a couple Feather 32u4 LoRa RFM9x boards and managed to get them talking with copious amounts of help from online tutorials. The idea was to have a number of sensors around my rather remote Oregon Coast forest property that would let me know when animals, people or vehicles were traveling through. With one set as a transmitter and using a battery, I walked the property and, by pushing a button, it connected to the unit in the house, which sent back a confirmation that blinked the LED on the transmitting unit several times for confirmation.
Our small 'mom & pop' farm would like to offer camping to hikers along the new C2C trail which passes across our forest road some 2.4 miles (by road) above us. Because of the 2.4 mile distance off the C2C trail that hikers would have to travel to stay here, we would like to offer a pickup service to the hikers at the trail kiosk. It is located just about one mile away, line-of-sight. The idea is to have a simple interface of a few toggle switches that they would set with a code, and then use a switch press to send the packet to us. Our end would receive it and reply in order to light or blink an LED to let them know the notice was sent.
My current setup Feather unit sends a number from the remote transmitter (or sensor) which is received at the house and the number is used to set levels on pin outputs, in binary, which would light LEDs and/or set off an audible indicator to know which sensor/transmitter had fired. The terminal output of the house unit showed the following info:
"4/8/2023 2:26:10 PM",HOUSE RECEIVER:
"4/8/2023 2:26:10 PM",Received: 02 Vbat= 3.860 Attempt 3886 with RSSI= -57
"4/8/2023 2:26:10 PM",Sent reply: And hello back to you, 02
"4/8/2023 2:26:11 PM",Pinouts for sensor# 02: LOW LOW HIGH LOW
and indicates the sender number (also battery level, attempts, power and the lines that should go high or low). The reply back to the sending unit blinks the LED on the transmitter board for confirmation. I think I have much of the needed code in place such that lines on the trail kiosk transmitter could be used send the code (instead of the sensor number hard coded) using toggle switches, and if it is a known code, the house unit could reply back to blink the LEDs for the sender and alert us to go pick them up.
Of course this is hilly terrain, so there is no visual line-of-sight, but there is a ridge half way that seems like it could "see" both our property and the trail kiosk. So I know I will need at least one, and worst case (if the ridge top is too broad), two repeaters to get a signal back and forth. My one remote unit hooked up to a PIR sensor transmitted for about two months without intervention, so changing batteries on the ridge top unit(s) every two months would not be often enough to worry about any additional accessories like setting up any solar panels for the units. Also the simple 4" wire antennas were adequate for up to almost a quarter mile through trees and even behind hills, so if I can get the ridge unit to be line-of-sight, I may not even need fancier antennas.
I know that in the distant past, I had seen more than one example of setting, specifically, the Feather 32u4 Lora as a repeater, but haven't re-found them. Can anyone offer any suggestions, a reasonable path forward, or locations of any blogs or articles that explain how my repeater could be set up? Also, those Feather boards are my one and only foray into the Arduino IDE interface so I have very little practical experience otherwise ... so any other suggestions about my current code attempt would not go unappreciated!
Thank you for your time and consideration. Have a great day!
Bob
My current transmit and receive codes are below if interested ... computer crash since, hopefully backups are identical
...
Receiver ...
// HOUSE RECEIVER
// Feather9x_RX
#include <SPI.h>
#include <RH_RF95.h>
#define LED 13
#define RFM95_CS 8 // for Feather32u4 RFM9x
#define RFM95_RST 4 // for Feather32u4 RFM9x
#define RFM95_INT 7 // for Feather32u4 RFM9x
#define RF95_FREQ 915.0
RH_RF95 rf95(RFM95_CS, RFM95_INT); // Singleton instance of the radio driver
void setup() {
pinMode(LED, OUTPUT);
pinMode(RFM95_RST, OUTPUT);
Serial.begin(115200);
while (!Serial) {
delay(1);
}
delay(10);
Serial.println("Feather LoRa HOUSE RECIEVER online.");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(20);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
while (1)
;
}
Serial.println("LoRa radio init OK!");
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1)
;
}
Serial.print("Set Freq to: ");
Serial.println(RF95_FREQ);
rf95.setTxPower(23, false);
Serial.println("Set Tx power to 23. ");
}
void loop() {
if (rf95.available()) { // Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len)) { // RH_RF95::printBuffer("Received: ", buf, len);
Serial.print("Got: ");
Serial.print((char*)buf);
Serial.print(" - with RSSI= ");
Serial.println(rf95.lastRssi(), DEC);
Serial.println();
uint8_t data[] = "And hello back to you";
rf95.send(data, sizeof(data));
rf95.waitPacketSent();
Serial.println(" Sent this reply: And hello back to you.");
Serial.println();
unsigned long currentMillis = millis();
unsigned long seconds = currentMillis / 1000;
unsigned long minutes = seconds / 60;
unsigned long hours = minutes / 60;
unsigned long days = hours / 24;
Serial.print(" Uptime: ");
currentMillis %= 1000;
seconds %= 60;
minutes %= 60;
hours %= 24;
Serial.print(days);
Serial.print(' ');
if (hours < 10)
Serial.print('0');
Serial.print(hours);
Serial.print(':');
if (minutes < 10)
Serial.print('0');
Serial.print(minutes);
Serial.print(':');
if (seconds < 10)
Serial.print('0');
Serial.println(seconds);
Serial.println();
Serial.println();
Serial.println();
//blink led three times ...
digitalWrite(LED, HIGH);
delay(300); // Wait 1/2 seconds
digitalWrite(LED, LOW);
delay(250); // Wait 1/2 seconds
digitalWrite(LED, HIGH);
delay(300); // Wait 1/2 seconds
digitalWrite(LED, LOW);
delay(250); // Wait 1/2 seconds
digitalWrite(LED, HIGH);
delay(300); // Wait 1/2 seconds
digitalWrite(LED, LOW);
} else {
Serial.println("Receive failed ");
}
}
}
Transmitter ...
// SENSOR TRANSMITTER - Feather9x_TX
#include <SPI.h>
#include <RH_RF95.h>
#include <Adafruit_SleepyDog.h>
// for feather32u4
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7
#define RF95_FREQ 915.0
#define VBATPIN A9
#define LED 13
#define SENSE 3
RH_RF95 rf95(RFM95_CS, RFM95_INT);
void setup() {
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
//pinMode(SENSE, INPUT);
//pinMode(SENSE, INPUT_PULLUP);
//digitalWrite(SENSE, LOW);
Serial.begin(115200);
while (!Serial) {
delay(1);
}
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
while (1)
;
}
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1)
;
}
Serial.print("Set Freq to: ");
Serial.println(RF95_FREQ);
rf95.setTxPower(23, false);
Serial.println("Set Tx power to 23. ");
//attachInterrupt(digitalPinToInterrupt(SENSE), sendIt, RISING);
Serial.println("Delaying 30 seconds ...");
delay(30000); // keep this to make it easier to upload changes ... gives 30 secs before running loop ...
}
int16_t packetnum = 0; // packet counter, we increment per transmission
void loop() {
Serial.println("Beginning loop: Waiting 5 seconds ...\n\n\n ");
delay(5000);
//rf95.sleep();
//Watchdog.sleep(60000);
// We resume here after the Interrupt
float measuredvbat = analogRead(VBATPIN);
measuredvbat *= 2; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1024; // convert to voltage
double integer;
float fractional = modf(measuredvbat, &integer);
fractional *= 1000;
int wfrac = fractional;
char radiopacket[38] = "10 Vbat= Attempt # ";
int r = 16;
while (r > 15) {
r = rand() % 16;
r++;
}
char sen[2];
itoa(r, sen, 10);
if (strlen(sen) == 2) {
strncpy(radiopacket, sen, strlen(sen));
} else {
strncpy(radiopacket, "0", 1);
strncpy(radiopacket + 1, sen, strlen(sen));
}
char vbat[1];
itoa(integer, vbat, 10);
strncpy(radiopacket + 9, vbat, strlen(vbat));
Serial.print("REMOTE SENSOR: random sensor= ");
Serial.println(r);
Serial.print("VBat: ");
Serial.print(measuredvbat);
Serial.print(", Integer = ");
Serial.print(vbat);
Serial.print(", Fraction = ");
Serial.println(wfrac);
//if(senseState == HIGH){
// Serial.println(" INT BUTTON PRESSED >>> ");
// senseState = LOW ;
//}
strncpy(radiopacket + 10, ".", 1);
char vbat2[3];
itoa(wfrac, vbat2, 10);
if (wfrac < 10) {
strncpy(radiopacket + 11, "00", 3);
strncpy(radiopacket + 13, vbat2, strlen(vbat2));
} else if (wfrac < 100) {
strncpy(radiopacket + 11, "0", 2);
strncpy(radiopacket + 12, vbat2, strlen(vbat2));
} else {
strncpy(radiopacket + 11, vbat2, strlen(vbat2));
}
itoa(packetnum++, radiopacket + 23, 10);
radiopacket[37] = 0;
Serial.print("Sending ");
Serial.println(radiopacket);
delay(10);
rf95.send((uint8_t *)radiopacket, sizeof(radiopacket));
rf95.waitPacketSent();
delay(10);
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.waitAvailableTimeout(5000)) {
// Should be a reply message for us now
if (rf95.recv(buf, &len)) {
Serial.print("Got reply: ");
Serial.println((char *)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
Serial.println("Waiting 10 seconds after valid reply to send again ...");
delay(10000); // Wait 10 seconds between transmits, could also 'sleep' here!
} else {
Serial.println("Receive failed");
}
} else {
Serial.println("Five seconds with no listener reply, transmitting again ...");
}
if (rf95.waitAvailableTimeout(1000)) { // Should be a reply message for us now
if (rf95.recv(buf, &len)) {
Serial.print("Got reply: ");
Serial.print((char *)buf);
Serial.print(" with RSSI= ");
Serial.println(rf95.lastRssi(), DEC);
// Serial.println("Waiting 10 seconds for next attempt ... \n\n");
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(LED, LOW);
delay(200);
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(LED, LOW);
//delay(5000);
} else {
Serial.println("Receive failed.");
}
} else {
Serial.println("No reply. \n\n\n");
if (rf95.available()) {
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
}
digitalWrite(LED, HIGH);
delay(600);
digitalWrite(LED, LOW);
//delay(5000) ;
}
//attachInterrupt(digitalPinToInterrupt(SENSE), sendIt, RISING);
}
void sendIt() {
detachInterrupt(digitalPinToInterrupt(SENSE));
}