README
LoRa
End-to-end LoRa communication system: bare-metal STM32 firmware, Go backend, TypeScript dashboard, Android range-test app, and a custom PCB design.
shell
[Phone + LoRa Board] --LoRa 433MHz--> [Base LoRa Board] --USB Serial--> [PC / Go Server]
|
:8080 HTTP + WS
|
[Web Dashboard]
Screenshots
Range Test Dashboard
Live Leaflet map with color-coded signal markers, heatmap overlay, and real-time stats.
Dev Kit Info Dashboard
System info, USB devices, serial ports, and LoRa module configuration via GraphQL.
Components
| Component | Stack | Description |
|---|---|---|
| Firmware | C99, bare-metal STM32F103, SX1262 | Transparent UART-to-LoRa serial bridge |
| Backend | Go 1.24, GraphQL, SQLite, WebSocket | Dev kit dashboard + range test server |
| Frontend | Vite + TypeScript | Hardware info dashboard |
| Range App | Android / Java | GPS + LoRa signal mapper |
| Hardware | KiCad | Dog tracker PCB (ESP32 + SX1262 + GPS) |
Hardware
- 2x DX-LR30 boards: STM32F103C8T6 + SX1262 LoRa + CH340C USB-serial
- Android phone with USB OTG
- Linux PC
Prerequisites
bash
# Firmware toolchain
sudo apt install gcc-arm-none-eabi stm32flash python3-serial
# Backend + Frontend
# Go 1.24+, Node.js + pnpm
go install github.com/air-verse/air@latest # Go hot reload
go install github.com/99designs/gqlgen/cmd/gqlgen@latest # GraphQL codegen
# Serial port access
sudo usermod -aG dialout $USER
Android SDK at ~/Android/Sdk for building the range test app.
Quick Start
Dev Kit Dashboard
bash
make install # pnpm install + go mod tidy
make dev # Start Go + Vite with hot reload
- Dashboard: http://localhost:5173
- GraphQL Playground: http://localhost:8080/playground
Range Test
bash
# One command: builds firmware, starts server
./dev-all.sh
# Or manually:
cd firmware && make
cd ../backend && CGO_ENABLED=1 go build -o bin/rangetest ./cmd/rangetest
sudo ./bin/rangetest -firmware ../firmware/build/firmware.bin -listen :8080
Downloads/lora_range_YYYYMMDD_HHmmss.csv on phoneFirmware Only
bash
cd firmware && make # Build
make flash # Flash to /dev/ttyUSB0
# Test serial bridge between two boards:
sudo screen /dev/ttyUSB0 9600 # Terminal 1
sudo screen /dev/ttyUSB1 9600 # Terminal 2
# Type in one -> appears in the other via LoRaTo enter bootloader mode: hold KEY, press RST, release KEY.
Android App
bash
cd range_app && ./gradlew assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
Make Targets
| Command | Description |
|---|---|
make dev | Start both servers with hot reload |
make dev-rangetest | Build firmware + range test server, run with sudo |
make build | Production build (Go binary + Vite bundle) |
make generate | Regenerate GraphQL code from schema |
make install | Install all dependencies |
make clean | Remove build artifacts |
Architecture
Backend (Hexagonal)
shell
infrastructure/ HTTP server, GraphQL, WebSocket
app/ Service layer
ports/ Interfaces (SystemReader, USBReader, SerialReader, LoRa)
adapters/ Implementations (sysfs, lsusb, serial, SQLite)
domain/ Pure data structs
Firmware (Bare-Metal C99)
No HAL, no RTOS. Direct register access. ~2.7KB flash.
| Module | Purpose |
|---|---|
main.c | UART <-> LoRa bridge loop |
sx1262.c | SX1262 LoRa radio driver |
uart.c | IRQ-driven USART1 with ring buffers |
spi.c | Hardware SPI1 @ 4.5 MHz |
gpio.c | Pin configuration |
system.c | 72 MHz clock setup, SysTick |
startup.c | Vector table, Reset_Handler |
LoRa Parameters
| Parameter | Value |
|---|---|
| Frequency | 433.0 MHz |
| Spreading Factor | 7 |
| Bandwidth | 125 kHz |
| Coding Rate | 4/5 |
| TX Power | +22 dBm |
| Max Packet | 255 bytes |
Binary Protocol
Packets are binary structs, base64-encoded over 9600 baud serial.
| Type | Code | Payload |
|---|---|---|
| Single Reading | 0x01 | seq(2) lat(4) lon(4) rssi(1) snr(1) time_offset(2) |
| Batch | 0x02 | count(1) + N x reading (max 13) |
| Home Location | 0x03 | lat(4) lon(4) |
| Version | 0x04 | major(1) minor(1) patch(1) hash(1) |
[RSSI:-45 SNR:12] <base64>\r\nSignal Strength
| RSSI | Quality | Color |
|---|---|---|
| > -70 dBm | Excellent | Green |
| -70 to -90 | Good | Yellow |
| -90 to -110 | Weak | Orange |
| < -110 dBm | Very Weak | Red |
Range Test API
| Route | Description |
|---|---|
GET / | Leaflet map dashboard |
GET /ws | WebSocket (init, reading, home messages) |
GET /api/readings | All readings JSON |
GET /api/session | Current session + home location |
GraphQL API (Dev Kit Dashboard)
graphql
type Query {
systemInfo: SystemInfo!
usbDevices: [USBDevice!]!
serialPorts: [SerialPort!]!
loraModules: [LoRaModule!]!
sendATCommand(port: String!, command: String!): ATResponse!
}
Measured Throughput
| Packet Size | Effective Rate |
|---|---|
| 1 byte | 29 B/s |
| 32 bytes | 225 B/s |
| 100 bytes | 258 B/s |
| 250 bytes | 273 B/s |
Hardware Design
The hardware/ directory contains a KiCad project for a dog tracker PCB (ESP32 + SX1262 + GPS + accelerometer). Includes schematic, routed PCB, Gerber files, BOM, and a JLCPCB ordering guide.
Project Structure
shell
firmware/ Bare-metal STM32 + SX1262 firmware
src/ C source files
include/ CMSIS headers, version defines
Makefile ARM GCC build
range_test_pc.py PC-side ping sender
test_throughput.py Throughput measurement
backend/ Go backend
cmd/server/ Dev kit dashboard entry point
cmd/rangetest/ Range test server entry point
domain/ Data structs
ports/ Interfaces
adapters/ Implementations (sysfs, lsusb, serial, SQLite, WebSocket)
app/ Service layer
infrastructure/ HTTP + GraphQL
frontend/ Vite + TypeScript dashboard
src/components/ Card renderers (system, USB, serial, LoRa)
src/api.ts GraphQL client
range_app/ Android range test app
app/src/main/java/ GPS, serial, map, binary protocol, CSV export
hardware/ KiCad dog tracker PCB
kicad-dog-tracker/ Schematic, PCB, Gerbers
BOM.csv Bill of materials