Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
033ddd6
Add GPS beacon and LXMF telemetry for T-Beam Supreme and Heltec V4
GlassOnTin Mar 12, 2026
09d1f64
Add R-Watch (T-Watch Ultra) board support with LoRa, GPS, PMU, RTC, a…
GlassOnTin Mar 27, 2026
ae02c5c
Add BHI260AP sensor hub integration for display GPIO expansion
GlassOnTin Mar 27, 2026
6ad9abe
Debug display + fix I2C bus bricked by deep sleep GPIO config
GlassOnTin Mar 27, 2026
f024489
Add T-Watch Ultra PCB reference photos
GlassOnTin Mar 27, 2026
9999333
Fix I2C bus failure — XPowersLib was using wrong default SDA/SCL pins
GlassOnTin Mar 27, 2026
4ddbe83
Add T-Watch Ultra developer notes documenting hard-won lessons
GlassOnTin Mar 27, 2026
504fb08
Enable CO5300 AMOLED display with live watch face
GlassOnTin Mar 27, 2026
b9319fa
Fix deep sleep: remove PMU enableSleep, fix GPIO handling, add shared…
GlassOnTin Mar 27, 2026
9f034e8
Add DRV2605 haptic driver with boot and sleep feedback
GlassOnTin Mar 27, 2026
84c7b88
Add BHI260AP sensor hub with deferred init
GlassOnTin Mar 27, 2026
deb0619
Add CST9217 touch panel with touch-to-wake display blanking
GlassOnTin Mar 27, 2026
8dd50ac
Add MAX98357A I2S speaker driver with tone generator
GlassOnTin Mar 27, 2026
c31b190
Add SPM1423 PDM microphone driver, fix speaker I2S port
GlassOnTin Mar 27, 2026
2c0c9f3
Add LVGL watch GUI with tileview navigation and serial screenshot
GlassOnTin Mar 28, 2026
54618f2
Fix custom font rendering, tearing, and scrollbar visibility
GlassOnTin Mar 28, 2026
76a45df
Add 28px Montserrat Bold font, full-frame rendering, clean scrolling
GlassOnTin Mar 28, 2026
7366a67
Optimize display: DMA SPI, on-demand screenshots, faster scroll
GlassOnTin Mar 28, 2026
2e9f703
Partial rendering, remote debug protocol, frame metrics
GlassOnTin Mar 28, 2026
3137220
Tune scroll feel: low-friction momentum, skip data updates during scroll
GlassOnTin Mar 28, 2026
1e83e6a
Add battery voltage, charge state, and temperature to GUI
GlassOnTin Mar 28, 2026
d3416a8
Add home button: short-press BOOT returns to watch face
GlassOnTin Mar 28, 2026
849d772
Center GPS coordinates, fix screenshot timing for blanked display
GlassOnTin Mar 28, 2026
9ad16bd
Add HDOP-based quality filtering to GPS display
GlassOnTin Mar 28, 2026
cb367ef
Enable BHI260AP wrist tilt wake and step counter
GlassOnTin Mar 28, 2026
b594284
Add IMU data logger to SD card with remote start/stop
GlassOnTin Mar 28, 2026
5e19294
Add shared SPI bus mutex for LoRa + SD card coexistence
GlassOnTin Mar 28, 2026
9263302
Fix 950ms main loop bottleneck: beacon and radio init on every iteration
GlassOnTin Mar 29, 2026
5a1f8eb
Add serial-triggered performance profile test
GlassOnTin Mar 29, 2026
4564eb9
Save profile baseline, add --save option to profile command
GlassOnTin Mar 29, 2026
c3b22f3
Self-provisioning, visible-tile-only updates, cleanup dead async code
GlassOnTin Mar 29, 2026
6d3ef44
Add provisioned radio profile baseline
GlassOnTin Mar 29, 2026
cc5bd6b
Enable radio on T-Watch: dev signature bypass, self-provisioning
GlassOnTin Mar 29, 2026
2f69313
Add provision-twatch_ultra and TWATCH_PORT to Makefile
GlassOnTin Mar 29, 2026
d49618a
Prevent beacon deep sleep on USB power, add beacon_gate to metrics
GlassOnTin Mar 29, 2026
32db2f2
Guard profiling for T-Watch only, fix T-Beam Supreme build
GlassOnTin Mar 29, 2026
1672a65
Add IFAC crypto test vectors — all 4 tests pass
GlassOnTin Mar 29, 2026
82bf97f
IFAC verified correct end-to-end, receiver not decoding LoRa frame
GlassOnTin Mar 29, 2026
6a43afb
Force radio restart on CMD_RADIO_STATE, debug LoRa RX failure
GlassOnTin Mar 29, 2026
5a0a0df
Confirmed: T-Beam Supreme RX hardware failure, not firmware
GlassOnTin Mar 29, 2026
c6e2a28
Fix LoRa RX: remove DC-DC regulator, restore demod reset after false …
GlassOnTin Mar 29, 2026
0cf5faa
Fix LXMF beacon: remove beacon_crypto_configured gate, add diagnostics
GlassOnTin Mar 30, 2026
2bf457e
Add Settings screen with display timeout, beacon, and GPS controls
GlassOnTin Mar 31, 2026
c3ab57f
Fix self-provisioning: write INFO_LOCK_BYTE and EEPROM checksum
GlassOnTin Mar 31, 2026
782710e
Extend sensor logger: tagged CSV, GPS/step/tilt/touch channels, Setti…
GlassOnTin Mar 31, 2026
9edf5a9
Add SD card file listing via debug command F
GlassOnTin Mar 31, 2026
d1daf8c
Add serial reset and bootloader commands, no more BOOT+RST
GlassOnTin Mar 31, 2026
a45986f
Add watchdog, serial file download, and bootloader-mode flash workflow
GlassOnTin Mar 31, 2026
7208ce3
Add BHI260 init retry, revert BMM150 firmware (no magnetometer on PCB)
GlassOnTin Apr 2, 2026
3d328d2
Add bubble level complication with EMA-filtered accelerometer
GlassOnTin Apr 2, 2026
3848b97
Bubble level: polar non-linear mapping and spring-damper fluid physics
GlassOnTin Apr 2, 2026
fded16c
Fix bubble level: sub-step spring-damper for stability at 500ms GUI rate
GlassOnTin Apr 2, 2026
b3f7a0f
Fix bubble level: remove lv_obj_center alignment clash, flip X axis
GlassOnTin Apr 2, 2026
8b3f67c
Adaptive bubble level: noise-driven filter and spring-damper tuning
GlassOnTin Apr 2, 2026
8bba734
Skip all GUI updates and LVGL rendering when display is blanked
GlassOnTin Apr 2, 2026
e75eb5a
Add tap-to-cycle battery complication with 4 display modes
GlassOnTin Apr 2, 2026
00c4997
Add tap-to-toggle LoRa and GPS complications
GlassOnTin Apr 2, 2026
ae0b9d1
Fix LoRa toggle: set beacon defaults when no radio config loaded
GlassOnTin Apr 2, 2026
816d34c
Fix LoRa toggle: handle 0xFF EEPROM defaults, stop gesture bubble
GlassOnTin Apr 2, 2026
bd34315
Show IFAC provisioning state on LoRa complication
GlassOnTin Apr 2, 2026
f11519a
Self-provision IFAC key from beacon network_name + passphrase
GlassOnTin Apr 2, 2026
76b3972
Separate build dirs and consolidated deploy targets
GlassOnTin Apr 2, 2026
2831ac0
Reset to watch face on display blank timeout
GlassOnTin Apr 6, 2026
99cc767
Add signal strength graph with LoRa direction finding
GlassOnTin Apr 6, 2026
b362e25
LoRa complication: show TX count in beacon mode, noise floor as fallback
GlassOnTin Apr 7, 2026
24f50c3
Increment stat_tx in beacon_transmit() for LoRa complication TX count
GlassOnTin Apr 7, 2026
50e81d0
Add Messages screen with received packet log and announce parser
GlassOnTin Apr 7, 2026
6b64f8c
Fix T-Beam CMD_RADIO_STATE: remove force-restart that broke SPI
GlassOnTin Apr 7, 2026
fd8a0be
Fix: don't write CONF_OK in self-provisioning (corrupts SX1262 calibr…
GlassOnTin Apr 7, 2026
5546676
Fix three LoRa blockers: stack overflow, warm reset, SPI mutex
GlassOnTin Apr 7, 2026
57b1bb7
Add LXMF message receive: decrypt, parse, display on Messages screen
GlassOnTin Apr 7, 2026
c9bd6a9
Add test_lxmf_send.py for OPPORTUNISTIC LXMF message testing
GlassOnTin Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ Release/*.zip
Release/*.json
Console/build
build/*
*.svd
debug.cfg
debug_custom.json
219 changes: 219 additions & 0 deletions Beacon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (C) 2026, GPS beacon support contributed by GlassOnTin

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#ifndef BEACON_H
#define BEACON_H

#if HAS_GPS == true

// Beacon interval and timing
uint32_t beacon_interval_ms = 30000; // Default 30s, configurable via Settings
#define BEACON_STARTUP_DELAY_MS 10000 // Wait 10s after boot before first beacon
// BEACON_NO_HOST_TIMEOUT_MS and last_host_activity defined in GPS.h
bool beacon_enabled = true; // Configurable via Settings

// Beacon interval options (ms) — indexed by roller selection
const uint32_t beacon_interval_options[] = { 10000, 30000, 60000, 300000, 600000 };
#define BEACON_INTERVAL_OPTIONS_COUNT 5

// Beacon radio parameters — must match the router's LoRa interface
#define BEACON_FREQ 868000000
#define BEACON_BW 125000
#define BEACON_SF 7
#define BEACON_CR 5
#define BEACON_TXP 17

// Beacon IFAC network — must match rnsd's RNodeInterface config
#define BEACON_NETWORK_NAME "helv4net"
#define BEACON_PASSPHRASE "R3ticulum-priv8-m3sh"

// Pre-computed RNS destination hash for PLAIN destination "rnlog.beacon"
// Computed as: SHA256(SHA256("rnlog.beacon")[:10])[:16]
const uint8_t RNS_DEST_HASH[16] = {
0x18, 0xbc, 0xd8, 0xa3, 0xde, 0xa1, 0x6e, 0xf6,
0x76, 0x5c, 0x6b, 0x27, 0xd0, 0x08, 0xd2, 0x20
};

// RNS packet header constants (PLAIN destination, HEADER_1, DATA)
// FLAGS: header_type=0 (HEADER_1), propagation=0 (BROADCAST),
// destination=0 (PLAIN), packet_type=2 (DATA), transport=0
#define RNS_FLAGS 0x08
#define RNS_CONTEXT 0x00

// Beacon state
bool beacon_mode_active = false;
uint32_t last_beacon_tx = 0;

// Forward declarations from main firmware
void lora_receive();
bool startRadio();
void setTXPower();
void setBandwidth();
void setSpreadingFactor();
void setCodingRate();
void beacon_transmit(uint16_t size);

// Diagnostic: track which gate blocks beacon_update()
// 0=not called, 1=host active, 2=startup delay, 3=radio offline,
// 4=no gps fix, 5=interval wait, 6=beacon sent
uint8_t beacon_gate = 0;

void beacon_check_host_activity() {
last_host_activity = millis();
if (beacon_mode_active) {
beacon_mode_active = false;
}
}

void beacon_update() {
if (!beacon_enabled) { beacon_gate = 0; return; }
// Don't beacon if host has been active recently
if (last_host_activity > 0 &&
(millis() - last_host_activity < BEACON_NO_HOST_TIMEOUT_MS)) {
beacon_gate = 1;
return;
}

// Wait for startup delay after boot
if (millis() < BEACON_STARTUP_DELAY_MS) {
beacon_gate = 2;
return;
}

// No point beaconing without a GPS fix — check BEFORE touching
// radio params to avoid putting the radio into standby needlessly
if (!gps_has_fix) {
beacon_gate = 4;
return;
}

// Radio must be online — restart if needed
if (!radio_online) {
lora_freq = (uint32_t)868000000;
lora_bw = (uint32_t)125000;
lora_sf = 7;
lora_cr = 5;
lora_txp = 17;
if (!startRadio()) {
beacon_gate = 3;
return;
}
} else {
// Radio is online but may have host/EEPROM params — force beacon settings
lora_freq = (uint32_t)868000000;
lora_bw = (uint32_t)125000;
lora_sf = 7;
lora_cr = 5;
lora_txp = 17;
setTXPower();
setBandwidth();
setSpreadingFactor();
setCodingRate();
}

// Respect beacon interval
if (last_beacon_tx > 0 &&
(millis() - last_beacon_tx < beacon_interval_ms)) {
beacon_gate = 5;
return;
}

beacon_mode_active = true;
beacon_gate = 6;

// LXMF path: send proper LXMF message with FIELD_TELEMETRY directly to Sideband
if (lxmf_identity_configured) {
// Periodic LXMF announce (every 10 minutes)
lxmf_announce_if_needed("RNode GPS Tracker");

// Get Unix timestamp from GPS directly
uint32_t timestamp = (uint32_t)(millis() / 1000);
#if HAS_GPS == true
{
extern TinyGPSPlus gps_parser;
if (gps_parser.date.isValid() && gps_parser.time.isValid() && gps_parser.date.year() >= 2024) {
uint32_t days = 0;
uint16_t yr = gps_parser.date.year();
uint8_t mo = gps_parser.date.month();
for (uint16_t y = 1970; y < yr; y++)
days += (y % 4 == 0) ? 366 : 365;
static const uint16_t mdays[] = {0,31,59,90,120,151,181,212,243,273,304,334};
if (mo >= 1 && mo <= 12) {
days += mdays[mo - 1];
if (mo > 2 && (yr % 4 == 0)) days++;
}
days += gps_parser.date.day() - 1;
timestamp = days * 86400UL + gps_parser.time.hour() * 3600UL
+ gps_parser.time.minute() * 60UL + gps_parser.time.second();
}
}
#endif

lxmf_beacon_send(gps_lat, gps_lon, gps_alt,
gps_speed, gps_hdop,
timestamp, (int)battery_percent);
last_beacon_tx = millis();
return;
}

// Legacy path: JSON payload for rnlog collector (no LXMF identity)
char json_buf[256];
int json_len = snprintf(json_buf, sizeof(json_buf),
"{\"lat\":%.6f,\"lon\":%.6f,\"alt\":%.1f,"
"\"sat\":%d,\"spd\":%.1f,\"hdop\":%.1f,"
"\"bat\":%d,\"fix\":%s}",
gps_lat, gps_lon, gps_alt,
gps_sats, gps_speed, gps_hdop,
(int)battery_percent,
gps_has_fix ? "true" : "false");

if (json_len <= 0 || json_len >= (int)sizeof(json_buf)) return;

if (beacon_crypto_configured) {
// Encrypted SINGLE packet (legacy JSON to rnlog.collector)
tbuf[0] = 0x00; // FLAGS: HEADER_1, BROADCAST, SINGLE, DATA
tbuf[1] = 0x00; // HOPS
memcpy(&tbuf[2], collector_dest_hash, 16);
tbuf[18] = 0x00; // CONTEXT_NONE

int crypto_len = beacon_crypto_encrypt(
(uint8_t*)json_buf, json_len,
collector_pub_key, collector_identity_hash,
&tbuf[19]
);

if (crypto_len > 0 && (19 + crypto_len) <= (int)MTU) {
beacon_transmit(19 + crypto_len);
lora_receive();
last_beacon_tx = millis();
}
} else {
// Fallback: PLAIN beacon (unencrypted, original behavior)
tbuf[0] = RNS_FLAGS; // 0x08 = PLAIN DATA
tbuf[1] = 0x00;
memcpy(&tbuf[2], RNS_DEST_HASH, 16);
tbuf[18] = RNS_CONTEXT;

memcpy(&tbuf[19], json_buf, json_len);
if (19 + json_len <= (int)MTU) {
beacon_transmit(19 + json_len);
lora_receive();
last_beacon_tx = millis();
}
}
}

#endif
#endif
Loading