November 2022
LoRa will be used for wireless communication between the PIR sensors and the device inside the RV. 12 VDC power will be routed to each PIR sensor + LoRa Feather mounted to the perimeter of the RV. PIR motion sensors that are available will be assessed to find the one with the best performance.
Adafruit M4 Express ATSAMD51 Cortex M4 with a Adafruit FeatherWing Music Maker and a LoRa FeatherWingstacked on top. A latching pushbutton with LED will turn on/off 12 VDC power to the device, and indicate that power is on via the built-in pushbutton LED. A custom PCB will provide power to the M4 Express + Music Maker via the Vbus connection, and connections for:
The FeatherWing Music Maker and LoRa FeatherWing for the receiving unit that will be located in the RV consume a lot of pins. With careful assignment of pins for the LoRa, I was able to allocate the pins required from the M4 Express ATSAMD51 Cortex M4 as shown in the table below.
Custom PCB |
Feather Music Maker |
Feather M4 Express |
LoRa | | | Custom PCB |
Feather Music Maker |
Feather M4 Express |
LoRa |
---|---|---|---|---|---|---|---|---|
RST | RST | RST | | | --- | --- | |||
3V3 | 3V3 | | | --- | --- | ||||
ARf | | | --- | ||||||
GND | GND | GND | | | --- | --- | |||
Play Signal |
A0 D14 | | | Vbat | |||||
Ext LED | A1 D15 | | | EN | |||||
A2 D16 | CS | | | 5V PS | Vbus | ||||
A3 D17 | RST | | | D13 LED |
|||||
A4 D18 | IRQ | | | ||||||
| | ||||||||
SCK | D25 SCK |
SCK | | | XDCS | D10 | |||
MOSI | D24 MOSI |
MOSI | | | DREQ | D9 | |||
MISO | 23 MISO |
MISO | | | MP3CS | D6 | |||
| | SD CS | D5 | ||||||
D1 | MIDI RX | | | D21 SCL |
|||||
| | D22 SDA |
Color Key: SPI I2C (I2C pullup on FeatherWing, not Feather)
Breadboard Schematic
PCB
A OpenPIR sensor is connected to an integrated Feather 32u4 with LoRa to sense mammal motion and transmit a message to the receiving LoRa device inside the RV.
The LoRa software chosen was the RadioHead Reliable Datagram library. The "Reliable Datagram" mode is reliable in the sense that messages are acknowledged by the recipient, and unacknowledged messages are retransmitted until acknowledged or the retries are exhausted. However, my testing found that rarely a transmission would not be acknowledged, and ocassionally the acknowledgement by the receiving unit would not occur.
I wanted to treat the PIR digital output as an event that I didn't want to miss, rather than as a sensor value to be repeated. So I built into the code persistant transmission of the message, and persistant acknowledgement (with a timeout). I also made sure communication between the Tx and Rx device was possible by sending a communication confirmation message between the two if none had occurred for at least 55 seconds. If no message is received by the receiving unit for 60 seconds, then a visual indication is shown on the Rx device.
The Rx unit will ignore messages from any other unit other than one with an address expected. The LoR message consists of an unencrypted byte array of size 16. This was chosen so that although I send simple small integers (0 to 255) between the Tx and Rx device, I could have encrypted those messages, and sent floating point sensor values. This allows the code to be easily adapted for other purposes.
/*
Feather ATmega32u4 + LoRa
Adafruit Product ID 3078
Send message via LoRa reliable datagram, and if any fails, repeat Tx
until the message is sent.
The reliability of this sketch at 1 Hz is nearly 100%.
ATmega32u4 chip running at 8 MHz
Built-in LED on pin #13
Battery voltage measurement on pin A9 (#9)
Hardware UART is Serial1 on pins #0 and #1
Built-in LED:
Quick flash - LoRa Tx/Rx
Constant on - Tx not acknowledged by other LoRa device.
*/
// In Arduino IDE install "Adafruit AVR Boards".
// In Arduino IDE, select 'Tools','Board','Adafruit Boards','Adafruit Feather 32u4'
// Find USB port in Windows by looking at ...
/////////////////////////////////////////////////////////////////////////
#define pinBuiltInLED 13
byte stateBuiltInLED = LOW;
// DEBUG turns on/off serial output
#define DEBUG false
void blinkERR(byte ledPIN){
// S-O-S
const int S = 150, O = 300;
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(O);
digitalWrite(ledPIN, LOW);
delay(O);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
} // blinkERR()
/////////////////////////////////////////////////////////////////////////
// Download the entire library from (look for .zip file link at top of page):
// http://www.airspayce.com/mikem/arduino/RadioHead/index.html
// Feather 32u4 with LoRa (single device)
#include <SPI.h>
#include <RH_RF95.h>
#include <RHReliableDatagram.h>
#define SENDER_ADDRESS 1 // This device (primary Tx unit).
#define RECEIVER_ADDRESS 2 // The other LoRa device that receives messages.
#define RFM95_CS 8 //D8 or Arduino pin #2
#define RFM95_RST 4 //D4 or Arduin pin #25
#define RFM95_INT 7 //D7 or Arduino pin #1
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
// Class to manage message delivery and receipt, using the rf95 declared above
RHReliableDatagram manager(rf95, SENDER_ADDRESS);
// Define data to be sent via LoRa
byte arrData[16]; // holds data to be sent via LoRa
byte buf[RH_RF95_MAX_MESSAGE_LEN]; // holds reply from receiver
unsigned long tx_count = 0;
bool tx_successful = true;
//////////////////////////////////////////////////////////////////////////////
#define pin_pir A0
unsigned long last_trigger = 0;
#define pin_ext_led A1
byte last_pir_state = LOW;
unsigned long last_debounce = 0; // the last time the output pin was toggled
unsigned long debounce_delay = 100; // The switch debounce time. 50 to 100 ms
unsigned long last_pir_tx = 0;
//////////////////////////////////////////////////////////////////////////////
// TimerA
// 10000000 us = 10000 ms = 10 sec = 0.1 Hz
// 1000000 us = 1000 ms = 1 sec = 1 Hz
// 100000 us = 100 ms = 0.1 sec = 10 Hz
// 10000 us = 10 ms = 0.01 sec = 100 Hz
// 1000 us = 1 ms = 0.001 sec = 1 kHz
unsigned long timer_a_lap = millis(); // timer
void sendLoRaMsg(byte *arr, int len) {
#if DEBUG
Serial.print("arr: '");
for (int i=0; i<len; i++) {
Serial.print(char(arr[i]));
}
Serial.println("'");
#endif
tx_successful = false;
tx_count++;
unsigned long ack_count = 0;
unsigned long ack_timer = millis();
// Persistently send LoR message..
do {
ack_count++;
// Method sendtoWait() will wait for an ack from the receipient.
if (manager.sendtoWait(arr, len, RECEIVER_ADDRESS)) {
// The receiver acknowledged the Tx
tx_successful = true;
digitalWrite(pinBuiltInLED, LOW);
digitalWrite(pin_ext_led, LOW);
#if DEBUG
Serial.println("sendtoWait successful");
#endif
} else {
#if DEBUG
Serial.println("sendtoWait failed!");
#endif
} // manager.sendtoWait()
#if DEBUG
Serial.print("tx_count = "); Serial.print(tx_count);
Serial.print("; ack_count = "); Serial.println(ack_count);
Serial.println();
#endif
} while (tx_successful == false && millis() - ack_timer < 10000);
delay(100); // Give GPIO chance to settle down after LoRa Tx
last_debounce = millis();
timer_a_lap = millis(); // reset the timer
} // sendLoRaMsg()
void timerA() {
// Send a LoRa message to the LoRa device every timer_interval_a
// to confirm that LoRa communication is working.
// The message is a random number between 0 and 254 (255 is PIR trigger).
if (timer_a_lap > millis()) timer_a_lap = millis();
if (millis() - timer_a_lap > 35000) {
digitalWrite(pinBuiltInLED, HIGH);
digitalWrite(pin_ext_led, HIGH);
int n = random(0,254);
for (int i=0; i<sizeof(arrData); i++) {
arrData[i] = 0x20; // space character
}
// ITOA() converts int to string
// char* itoa(int num, char* buffer, int base)
// The "(char*)" is a type cast that tells the compiler to treat arrData as a
// char pointer and not as a bype array. iota expect char* as the 2nd argument.
itoa(n, (char*)arrData, 10);
arrData[sizeof(arrData)-1] = 0x00; // null character
#if DEBUG
Serial.println("timerA() triggered!");
#endif
sendLoRaMsg(arrData, sizeof(arrData));
last_debounce = millis();
timer_a_lap = millis(); // reset the timer
}
} // timerA()
void sensorPIR() {
// Must implement a debounce timer for managing the PIR sensor digital
// output.
byte pir_state = digitalRead(pin_pir);
// if millis() or timer wraps around, reset it
if (last_debounce > millis()) last_debounce = millis();
if (last_pir_tx > millis()) last_pir_tx = millis();
// Wait at least 15 seconds between each PIR Tx to give the
// Music Maker time to play the last song.
if (pir_state != last_pir_state && millis() - last_pir_tx > 15000) {
// Multiple changes in the pir_state can occur when a pushbutton is
// pressed, or a switch is toggled. Use a debounce timer to only react
// to a change in button state after an interval of debounce_delay.
last_pir_state = pir_state;
// Check if enough time has passed to evaluate another pushbutton press.
if ((millis() - last_debounce) > debounce_delay) {
if (pir_state == HIGH) {
// PIR triggered!
last_pir_tx = millis();
digitalWrite(pinBuiltInLED, HIGH);
digitalWrite(pin_ext_led, HIGH);
// assign a space to arrData..
for (int i=0; i<sizeof(arrData); i++) {
arrData[i] = 0x20; // space character
}
// ITOA() converts int to string
// char* itoa(int num, char* buffer, int base)
// The "(char*)" is a type cast that tells the compiler to treat arrData as a
// char pointer and not as a bype array. iota expect char* as the 2nd argument.
itoa(255, (char*)arrData, 10);
arrData[sizeof(arrData)-1] = 0x00; // null character
#if DEBUG
Serial.println("sensorPIR() triggered!");
#endif
sendLoRaMsg(arrData, sizeof(arrData));
timer_a_lap = millis(); // reset timerA
} // pir_state
last_debounce = millis();
} // millis()
} // pir_state != last_pir_state
} // sensorPIR()
/////////////////////////////////////////////////////////////////////////
void setup() {
pinMode(pinBuiltInLED, OUTPUT);
digitalWrite(pinBuiltInLED, LOW);
pinMode(pin_ext_led, OUTPUT);
digitalWrite(pin_ext_led, LOW);
#if DEBUG
Serial.begin(115200);
while (!Serial) {
delay(100);
if (millis() % 2)
digitalWrite(pinBuiltInLED, HIGH);
else
digitalWrite(pinBuiltInLED, LOW);
}
Serial.println("\nSerial ready");
#endif
digitalWrite(pinBuiltInLED, LOW);
pinMode(pin_pir, INPUT);
//////////////////////////////////////////////////////////////////////////////
// LoRa
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!manager.init()) {
#if DEBUG
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
#endif
while (1) {
blinkERR(pinBuiltInLED);
blinkERR(pin_ext_led);
}
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
#if DEBUG
Serial.println("setFrequency failed");
#endif
while (1) {
blinkERR(pinBuiltInLED);
blinkERR(pin_ext_led);
}
}
#if DEBUG
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
#endif
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the
// PA_BOOST transmitter pin, then you can set the
// transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
//////////////////////////////////////////////////////////////////////////////
randomSeed(millis());
timer_a_lap = millis(); // reset the timer
#if DEBUG
Serial.println("setup finished\n");
#endif
} // setup()
void loop() {
timerA();
sensorPIR();
} // loop()
/*
Adafruit Feather M4 Express SAMD51
Adafruit Music Maker FeatherWing
LoRa FeatherWing (separate, stacked)
In Arduino IDE, set board to 'Adafruit Feather M4 Express (SAMD51)'
If bootloading frozen, click RST button twice quickly.
The red LED will pulse and the RGB LED will be green when you are
in bootloader mode.
The yellow “charging” LED flickers constantly whenever the Feather is powered by USB
Arduino C++ code or CircuitPython
WARNING: The Arduino IDE serial monitor causes setup() to wait until
the serial monitor IDE is run.
*/
/////////////////////////////////////////////////////////////////////////
const byte pin_built_in_led = 13;
// DEBUG turns on/off serial output
#define DEBUG false
void blinkERR(byte ledPIN){
// S-O-S
const int S = 150, O = 300;
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(O);
digitalWrite(ledPIN, LOW);
delay(O);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
} // blinkERR()
/////////////////////////////////////////////////////////////////////////
// Download the entire library from (look for .zip file link at top of page):
// http://www.airspayce.com/mikem/arduino/RadioHead/index.html
// Feather 32u4 with LoRa (single device)
#include <SPI.h>
#include <RH_RF95.h>
#include <RHReliableDatagram.h>
// LoRa FeatherWing with separate Feather M4 Express
#define RFM95_CS 16
#define RFM95_RST 17
#define RFM95_INT 18
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
// The two addresses are an integer of type uint8_t (0 to 255 decimal).
#define SENDER_ADDRESS 1 // The LoRa device that sends messages to this device
#define RECEIVER_ADDRESS 2 // This LoRa device
// Class to manage message delivery and receipt, using the rf95 declared above
RHReliableDatagram manager(rf95, RECEIVER_ADDRESS);
byte buf[RH_RF95_MAX_MESSAGE_LEN];
/////////////////////////////////////////////////////////////////////////
// Adafruit Music Maker FeatherWing
// Install the 'Adafruit_VS1053' & 'Adafruit_BusIO' libraries.
// https://github.com/adafruit/Adafruit_VS1053_Library
// https://github.com/adafruit/Adafruit_BusIO
// include SPI, MP3 and SD libraries
#include <SPI.h>
#include <SD.h>
#include <Adafruit_VS1053.h>
// These are the pins used
#define VS1053_RESET -1 // VS1053 reset pin (not used!)
// Feather M4 Express (or ESP32S2, 328, m0, nrf52840, 32u4)
#define VS1053_CS 6 // VS1053 chip select pin (output)
#define VS1053_DCS 10 // VS1053 Data/command select pin (output)
#define CARDCS 5 // Card chip select pin
// DREQ should be an Int pin *if possible* (not possible on 32u4)
#define VS1053_DREQ 9 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);
/////////////////////////////////////////////////////////////////////////
unsigned long last_rx = 0;
bool pir_triggered = false;
/////////////////////////////////////////////////////////////////////////
// Momentary pushbutton with LED
int pinPushButtonLED = 15;
int pinPushButtonPullDown = 14;
int buttonState = LOW; // buttonState & lastButtonState are initialized for a pulldown resistor
int lastButtonState = LOW;
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // The switch debounce time. 50 to 100 ms
void pushButton() {
// Detect a change in the pushbutton state and turn on the
// pushbutton LED in response.
// This logic is for a button using a pulldown resistor.
buttonState = digitalRead(pinPushButtonPullDown);
// if millis() or timer wraps around, reset it
if (lastDebounceTime > millis()) lastDebounceTime = millis();
if (buttonState != lastButtonState) {
// Multiple changes in the buttonState can occur when a pushbutton is
// pressed, or a switch is toggled. Use a debounce timer to only react
// to a change in button state after an interval of debounceDelay.
lastButtonState = buttonState;
// Check if enough time has passed to evaluate another pushbutton press.
if ((millis() - lastDebounceTime) > debounceDelay) {
lastDebounceTime = millis();
if (buttonState == HIGH) {
digitalWrite(pinPushButtonLED, HIGH);
pir_triggered = true;
} else {
digitalWrite(pinPushButtonLED, LOW);
pir_triggered = false;
} // buttonState
} // millis()
} // buttonState != lastButtonState
} // pushButton()
/////////////////////////////////////////////////////////////////////////
void setup() {
pinMode(pin_built_in_led, OUTPUT);
digitalWrite(pin_built_in_led, LOW);
#if DEBUG
Serial.begin(115200);
while (!Serial) {
delay(100);
if (millis() % 2)
digitalWrite(pin_built_in_led, HIGH);
else
digitalWrite(pin_built_in_led, LOW);
}
digitalWrite(pin_built_in_led, LOW);
Serial.println("\nSerial ready");
#endif
pinMode(pinPushButtonLED, OUTPUT);
for(uint8_t i = 5; i>0; i--){
digitalWrite(pinPushButtonLED, HIGH);
delay(30);
digitalWrite(pinPushButtonLED, LOW);
delay(30);
}
pinMode(pinPushButtonPullDown, INPUT);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!manager.init()) {
#if DEBUG
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
#endif
while (1) {
blinkERR(pin_built_in_led);
blinkERR(pinPushButtonLED);
}
}
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
#if DEBUG
Serial.println("setFrequency failed");
#endif
while (1) {
blinkERR(pin_built_in_led);
blinkERR(pinPushButtonLED);
}
}
#if DEBUG
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
#endif
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
// initialise the music player
if (! musicPlayer.begin()) {
#if DEBUG
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
#endif
while (1) {
blinkERR(pin_built_in_led);
blinkERR(pinPushButtonLED);
}
}
// Make a tone to indicate VS1053 is working
musicPlayer.sineTest(0x44, 500);
delay(1000);
musicPlayer.stopPlaying();
if (!SD.begin(CARDCS)) {
#if DEBUG
Serial.println(F("SD failed, or not present"));
#endif
while (1) {
blinkERR(pin_built_in_led);
blinkERR(pinPushButtonLED);
}
}
// Set volume (0 to 255) for left, right channels. lower numbers == louder volume!
musicPlayer.setVolume(0,0);
#if defined(__AVR_ATmega32U4__)
// Timer interrupts are not suggested, better to use DREQ interrupt!
// but we don't have them on the 32u4 feather...
musicPlayer.useInterrupt(VS1053_FILEPLAYER_TIMER0_INT); // timer int
#else
// If DREQ is on an interrupt pin we can do background
// audio playing
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); // DREQ int
#endif
musicPlayer.playFullFile("/dog003.wav"); // single bark
randomSeed(millis());
#if DEBUG
Serial.println("setup finished\n");
#endif
} // setup()
void loop() {
pushButton();
// Wait for a message addressed to us from the client
if (manager.available()) {
digitalWrite(pin_built_in_led, HIGH);
digitalWrite(pinPushButtonLED, HIGH);
uint8_t len = sizeof(buf);
uint8_t from;
bool ack_tx_successful = false;
unsigned long ack_timer = millis();
unsigned long ack_tx_count = 0;
last_rx = millis();
// Persistently send back a reply, or timeout after 30 sec.
do {
if (manager.recvfromAck(buf, &len, &from)) {
digitalWrite(pin_built_in_led, HIGH);
digitalWrite(pinPushButtonLED, HIGH);
// Only accept messages from RECEIVER_ADDRESS
if (from == SENDER_ADDRESS) {
int val = atoi((char*)buf);
if (val == 255) {
// PIR triggered on Tx device.
pir_triggered = true;
#if DEBUG
Serial.println("PIR triggered");
#endif
} else {
// Random number between 0 and 254 received to confirm
// LoRa communication between Rx and Tx device.
#if DEBUG
Serial.print("LoRa msg received number: "); Serial.print(val); Serial.print(" from:"); Serial.println(from);
#endif
}
} else {
#if DEBUG
Serial.print("Ignored LoRa msg from "); Serial.println(from);
#endif
} // RECEIVER_ADDRESS
ack_tx_successful = true;
}
digitalWrite(pin_built_in_led, LOW);
digitalWrite(pinPushButtonLED, LOW);
} while (ack_tx_successful == false && millis() - ack_timer < 30000);
// Check if an LoRa ack timeout occurred..
if (millis() - ack_timer > 29000) {
//blinkERR(pin_built_in_led);
digitalWrite(pinPushButtonLED, HIGH);
#if DEBUG
Serial.print("Ack timeout occurred "); Serial.print(ack_tx_count); Serial.println(" times!");
Serial.println();
#endif
}
} // manager.available()
// If the PIR sensor detected motion, then play
// dogs barking on the Feather Music Maker.
if (pir_triggered == true) {
digitalWrite(pinPushButtonLED, HIGH);
// Play the Feather Music Maker..
byte songs = random(0,4);
switch (songs) {
case 0:
musicPlayer.playFullFile("/dog001.wav"); // growling with 2x barks
musicPlayer.playFullFile("/dog003.wav"); // single bark
musicPlayer.playFullFile("/dog002.wav"); // multiple barking with some growling
break;
case 1:
musicPlayer.playFullFile("/dog003.wav"); // single bark
break;
case 2:
musicPlayer.playFullFile("/dog001.wav"); // growling with 2x barks
break;
case 3:
musicPlayer.playFullFile("/dog002.wav"); // multiple barking with some growling
break;
case 4:
musicPlayer.playFullFile("/dog003.wav"); // single bark
musicPlayer.playFullFile("/dog001.wav"); // growling with 2x barks
break;
} // switch
pir_triggered = false;
} else {
digitalWrite(pinPushButtonLED, LOW);
} // pir_triggered == true
if (millis() - last_rx > 60000) {
// The LoRa Tx device has not been in communication for a long time!
//blinkERR(pinPushButtonLED);
digitalWrite(pinPushButtonLED, HIGH);
#if DEBUG
Serial.print("\nWARNING: No LoRa contact from Rx device for "); Serial.print((millis() - last_rx)/1000); Serial.println(" sec !\n");
#endif
} // last_rx timeout
} // loop()
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.