Pre-requisites
🌳 An outdoor LoRa-GPS tracker with an E-Ink display 🔑
Send GPS information to the peer LoRa node and display it on the E-Ink screen
Pre-requisites
This example shows how 2 devices are exchanging GPS latitude-longitude information with each other via LoRa communication.
The serial output below shows after resetting the device, it initialises the LoRa and E-Ink, waits for a while, and then finally gets a GPS fix and starts to send that information via LoRa.
ls -al /dev/cu.usbmodem
and arduino-cli board list
.
make a
to upload the code to the PCB A with local address 0xAA
, destination address0xBB
, E-Ink version 2
make b
to upload the code to the PCB B with local address 0xBB
, destination address0xAA
, E-Ink version 1
#define DEBUG 1
// 0 = no debug
// 1 = debug
// 2 = debug + GPS info
#ifdef EINK_V2
#include "src/eink_v2/eink_v2.h"
#else
#include "src/eink/eink.h"
#endif
#include "src/debug/debug.h"
#include "src/gps/gps.h"
#include "src/led/led.h"
#include "src/lora/lora.h"
byte localAddress = LOCAL_ADDRESS;
byte destinationAddress = DESTINATION_ADDRESS;
int sendLoRaInterval = 3000; // Send LoRa packet every 3 seconds
uint32_t lastLoRaSendTime = 0;
int displayInterval = 10000; // display on E-Ink every 10 seconds
uint32_t lastDisplayTime = 0;
String dataFromDestinationAddress = "";
String gpsTime = "00:00";
String gpsDate = "2021-12-31";
String gpsLatLong = "waiting for fix";
// Example "1234.12345678N, 12345.12345678E"
String gpsLatLongForDisplay = "";
// Example "1 40N, 103 91E";
LatLong latLong = {0.00, 0.00, false};
LatLong prevLatLong = {0.00, 0.00, false};
LatLong peerLatLong = {0.00, 0.00, false};
Haversine haversine = {0.000, 0};
void setup() {
#ifdef DEBUG
SerialUSB.begin(9600);
delay(1000);
#endif
DEBUG_PRINT_MORE("Starting Oak demo on node " + String(localAddress, HEX));
DEBUG_PRINT_MORE(" localAddress: " + String(localAddress, HEX));
DEBUG_PRINT_MORE(" destinationAddress: " + String(destinationAddress, HEX));
if (!initLoRa()) {
DEBUG_PRINT("Starting LoRa failed!");
} else {
DEBUG_PRINT("Starting LoRa succeeded!");
}
if (!initEink()) {
DEBUG_PRINT("Starting Eink failed!");
} else {
DEBUG_PRINT("Starting Eink succeeded!");
}
displayOnEink("waiting for fix", "", "searching", "for peer");
initGPS();
}
void loop() {
// Send LoRa packet to peer node
if (millis() - lastLoRaSendTime > sendLoRaInterval) {
if (hasNewGPSFix(&prevLatLong, &latLong)) {
sendLoRa(gpsLatLong, localAddress, destinationAddress);
DEBUG_PRINT_MORE("Send data "
+ gpsLatLong
+ " from 0x"
+ String(localAddress, HEX)
+ " to 0x"
+ String(destinationAddress, HEX));
lastLoRaSendTime = millis();
sendLoRaInterval = random(2000) + 1000;
}
}
// Receive LoRa packet
if (receiveLoRa(
LoRa.parsePacket(), localAddress, dataFromDestinationAddress)) {
DEBUG_PRINT_MORE("Received data "
+ dataFromDestinationAddress
+ " from 0x"
+ String(destinationAddress, HEX)
+ " to 0x"
+ String(localAddress, HEX));
convertStringToLatLong(dataFromDestinationAddress, &peerLatLong);
}
// Receive GPS fix information
if (receivedGPSfix()) {
getGPStime(gpsTime);
getGPSdate(gpsDate);
getLatLong(&latLong);
printGPSinfo();
if (millis() - lastDisplayTime > displayInterval) {
if (hasNewGPSFix(&prevLatLong, &latLong)) {
convertLatLongForDisplay(&latLong, gpsLatLongForDisplay);
// Both local and peer nodes have GPS fixes around the same time
if (isOKtoCalculateHaversine(&latLong, &peerLatLong)) {
getHaversineDistance(&latLong, &peerLatLong, &haversine);
DEBUG_PRINT_MORE("Haversine distance: "
+ String(haversine.distance, 3)
+ " km "
+ String(haversine.timeDiff, DEC)
+ "s ago");
displayOnEink(
gpsLatLongForDisplay,
gpsTime,
String(haversine.distance, 3) + "km",
String(haversine.timeDiff) + "s ago");
} else {
// Peer node had a GPS fix a while ago
if (peerLatLong.hasValidFix) {
int timeDiff = calculateTimeDiff(peerLatLong.timestamp);
DEBUG_PRINT("CANNOT calculate Haversine distance.");
DEBUG_PRINT_MORE("Haversine distance: "
+ String(haversine.distance, 3) + " km");
DEBUG_PRINT_MORE("Haversine time difference: "
+ String(timeDiff, DEC) + "s ago");
displayOnEink(
gpsLatLongForDisplay,
gpsTime,
String(haversine.distance, 3) + "km",
String(timeDiff, DEC) + "s ago");
} else {
// Local node does not have a GPS fix
displayOnEink(
gpsLatLongForDisplay, gpsTime, "searching", "for peer");
}
} // end if (isOKtoCalculateHaversine)
prevLatLong = latLong;
} else {
DEBUG_PRINT_MORE(
"No GPS fix yet. Date: " + gpsDate +
" Time: " + gpsTime +
" Lat/Long: " + gpsLatLong);
fastBlink(4);
}
lastDisplayTime = millis();
} // if (millis() - lastDisplayTime > displayInterval)
} // if (receivedGPSfix())
}
void printGPSinfo() {
DEBUG_GPS("----------------------------------------");
DEBUG_GPS("Date: " + gpsDate);
DEBUG_GPS("Time: " + gpsTime);
convertLatLongToString(&latLong, gpsLatLong);
DEBUG_GPS("Lat/Long: " + gpsLatLong);
DEBUG_GPS("GPS Fix? " + String(getGPSfix(), DEC));
DEBUG_GPS("GPS Fix quality: " + String(getGPSfixquality(), DEC));
DEBUG_GPS("Speed (knots): " + String(getGPSspeed()));
DEBUG_GPS("Angle: " + String(getGPSangle()));
DEBUG_GPS("Altitude: " + String(getGPSaltitude()));
DEBUG_GPS("Satellites: " + String(getGPSsatellites()));
DEBUG_GPS("Time [s] since last fix: "
+ String(getGPStimeSinceLastFix(), 3));
DEBUG_GPS(" since last GPS time: "
+ String(getGPSlastTime(), 3));
DEBUG_GPS(" since last GPS date: "
+ String(getGPSlastDate(), 3));
}
BOARD?=hutscape:samd:oak
PORT := $(shell ls /dev/cu.usbmodem*)
BUILD=build
.PHONY: default a b lint pcb_a pcb_b flash clean
default: lint pcb_a flash clean
a: lint pcb_a flash clean
b: lint pcb_b flash clean
lint:
cpplint --extensions=ino --filter=-legal/copyright,-whitespace/line_length,-readability/casting,-readability/todo,-runtime/int *.ino
pcb_a:
rm -f src/eink*
ln -sf ../lib/eink_v2 src
arduino-cli compile --clean --fqbn $(BOARD) --build-property compiler.cpp.extra_flags="-DLOCAL_ADDRESS=0xAA -DDESTINATION_ADDRESS=0xBB -DEINK_V2" --output-dir $(BUILD) ./
rm src/eink_v2
pcb_b:
rm -f src/eink*
ln -sf ../lib/eink src
arduino-cli compile --clean --fqbn $(BOARD) --build-property compiler.cpp.extra_flags="-DLOCAL_ADDRESS=0xBB -DDESTINATION_ADDRESS=0xAA" --output-dir $(BUILD) ./
rm src/eink
flash:
arduino-cli upload -p $(PORT) --fqbn $(BOARD) --input-dir $(BUILD) --verbose
clean:
rm -r build