Oak

🌳 An outdoor LoRa-GPS tracker with an E-Ink display 🔑

power 18650
wireless LoRa,
sensor GPS, E-Ink
mcu SAMD21G18
bom items 82
bom cost USD $78.93
vendors 6
completed July 2022

LoRa duplex node A

LoRa duplex p2p medium

LoRa transmit and receive from destination address approximately at the same time


Before starting

Dependancies

Arduino LoRa

Details

Run both nodes AA and BB at the same time to exchange an integer.

This example can be run battery-powered. The LED will blink when the node sends out a packet.

Steps

  1. Prepare LoRa duplex B to send and receive at the same time
  2. Plug in the Oak PCB to the computer
  3. (Option A) Ensure the PCB can be detected with ls -al /dev/cu.usbmodem and arduino-cli board list. Run make to compile and upload the code to the board. (Option A) Ensure the PCB can be detected with <code>ls -al /dev/cu.usbmodem</code> and <code>arduino-cli board list</code>. Run <code>make</code> to compile and upload the code to the board.
  4. (Option B) Ensure the board can be detected with Arduino IDE. Compile and upload the code to the board. (Option B) Ensure the board can be detected with Arduino IDE. Compile and upload the code to the board.

Serial console

Serial output from the firmware.

Code

Download code
#include <LoRa.h>
#include <SPI.h>

int counter = 0;
#define LEDPIN 2
int ledState = LOW;

#define RADIO_CS_PIN 5        // D5 on Arduino Zero
#define RADIO_DI0_PIN 11      // D11 on Arduino Zero
#define RADIO_RST_PIN 6       // D6 on Arduino Zero
#define LORA_FREQUENCY 915E6  // 915 MHz Antenna and LoRa module

byte localAddress = 0xAA;
byte destinationAddress = 0xBB;
long lastSendTime = 0;
int interval = 2000;

void setup() {
  SerialUSB.begin(9600);
  pinMode(LEDPIN, OUTPUT);

  SerialUSB.println("Starting LoRa duplex on node "
    + String(localAddress, HEX));
  pinMode(LEDPIN, OUTPUT);

  LoRa.setPins(RADIO_CS_PIN, RADIO_RST_PIN, RADIO_DI0_PIN);

  if (!LoRa.begin(LORA_FREQUENCY)) {
    SerialUSB.println("Starting LoRa failed!");
    while (1) {
    }
  }
}

void loop() {
  if (millis() - lastSendTime > interval) {
    String sensorData = String(counter++);
    sendMessage(sensorData);

    SerialUSB.print("Sending data " + sensorData);
    SerialUSB.print(" from 0x" + String(localAddress, HEX));
    SerialUSB.println(" to 0x" + String(destinationAddress, HEX));

    lastSendTime = millis();
    interval = random(2000) + 1000;

    // Toggle LED
    ledState = !ledState;
    digitalWrite(LEDPIN, ledState);
    SerialUSB.println("LED state " + String(ledState));
  }

  receiveMessage(LoRa.parsePacket());
}

void sendMessage(String outgoing) {
  LoRa.beginPacket();
  LoRa.write(destinationAddress);
  LoRa.write(localAddress);
  LoRa.write(outgoing.length());
  LoRa.print(outgoing);
  LoRa.endPacket();
}

void receiveMessage(int packetSize) {
  if (packetSize == 0) return;

  int recipient = LoRa.read();
  byte sender = LoRa.read();
  byte incomingLength = LoRa.read();

  String incoming = "";

  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }

  if (incomingLength != incoming.length()) {
    SerialUSB.println("Error: Message length does not match length");
    return;
  }

  if (recipient != localAddress) {
    SerialUSB.println("Error: Recipient address does not match local address");
    return;
  }

  SerialUSB.print("Received data " + incoming);
  SerialUSB.print(" from 0x" + String(sender, HEX));
  SerialUSB.println(" to 0x" + String(recipient, HEX));
}

Makefile

BOARD?=hutscape:samd:oak
PORT := $(shell ls /dev/cu.usbmodem*)
BUILD=build

.PHONY: default lint all flash clean

default: lint all flash clean

lint:
	cpplint --extensions=ino --filter=-legal/copyright,-whitespace/line_length,-readability/casting,-readability/todo,-runtime/int *.ino

all:
	arduino-cli compile --fqbn $(BOARD) --output-dir $(BUILD) ./

flash:
	arduino-cli upload -p $(PORT) --fqbn $(BOARD) --input-dir $(BUILD) --verbose

clean:
	rm -r build

References