Public Access
The Sniffer lists packets newest first (time, RSSI, SNR, and for Meshtastic the sender, receiver and hops), with the clear header and a hex dump on Enter (Q96); `p` picks an EU868 preset, kept in Settings (Q95). `c` starts a Capture: pcap with LoRaTap in /captures/lora, its own Clean-up category, recorded by a small Service so it carries on with the App closed (Q97, Q100). The Status Bar shows L while listening, bright on each packet, and CAP while capturing (Q101). StorageService gains raw appends for binary files. Debug Builds get `lora inject` to test all of this with no transmitter in range: a Capture made on the device reads back in TShark field for field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
65 lines
2.4 KiB
C++
65 lines
2.4 KiB
C++
#include "loratap.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
|
|
namespace roro::lora {
|
|
|
|
static void le16(std::vector<uint8_t>& o, uint16_t v) { o.insert(o.end(), {uint8_t(v), uint8_t(v >> 8)}); }
|
|
static void le32(std::vector<uint8_t>& o, uint32_t v) {
|
|
o.insert(o.end(), {uint8_t(v), uint8_t(v >> 8), uint8_t(v >> 16), uint8_t(v >> 24)});
|
|
}
|
|
|
|
// LoRaTap's dBm encoding: -139 dBm plus the byte, clamped to what a byte holds.
|
|
static uint8_t dbmByte(float dbm) {
|
|
long v = std::lround(dbm + 139);
|
|
return static_cast<uint8_t>(std::clamp(v, 0L, 255L));
|
|
}
|
|
|
|
std::string capturePath(int64_t utcSeconds) {
|
|
time_t t = static_cast<time_t>(utcSeconds);
|
|
struct tm u;
|
|
gmtime_r(&t, &u);
|
|
char buf[48];
|
|
std::snprintf(buf, sizeof buf, "/captures/lora/%04d%02d%02d-%02d%02d%02d.pcap", u.tm_year + 1900, u.tm_mon + 1,
|
|
u.tm_mday, u.tm_hour, u.tm_min, u.tm_sec);
|
|
return buf;
|
|
}
|
|
|
|
void appendPcapHeader(std::vector<uint8_t>& out) {
|
|
le32(out, 0xA1B2C3D4);
|
|
le16(out, 2);
|
|
le16(out, 4);
|
|
le32(out, 0); // time zone
|
|
le32(out, 0); // timestamp accuracy
|
|
le32(out, 65535); // snap length
|
|
le32(out, 270); // LINKTYPE_LORATAP
|
|
}
|
|
|
|
void appendRecord(std::vector<uint8_t>& out, uint32_t seconds, uint32_t micros, const RxInfo& rx, const uint8_t* data,
|
|
size_t len) {
|
|
uint32_t captured = static_cast<uint32_t>(kLoraTapSize + len);
|
|
le32(out, seconds);
|
|
le32(out, micros);
|
|
le32(out, captured);
|
|
le32(out, captured);
|
|
|
|
uint32_t f = rx.frequencyHz;
|
|
// The spec puts packet RSSI in quarter dB below 0 dB SNR (an SX127x formula), but Wireshark
|
|
// reads it as -139 dBm plus the byte either way, and the SX1262 already gives dBm.
|
|
uint8_t packetRssi = dbmByte(rx.rssi);
|
|
long snr = std::clamp(std::lround(rx.snr * 4), -128L, 127L);
|
|
out.insert(out.end(), {
|
|
0, 0, 0, uint8_t(kLoraTapSize), // version 0, padding, length
|
|
uint8_t(f >> 24), uint8_t(f >> 16), uint8_t(f >> 8), uint8_t(f),
|
|
uint8_t(std::lround(rx.bandwidthKHz / 125)), rx.spreadingFactor,
|
|
packetRssi, dbmByte(rx.rssi), dbmByte(rx.noiseFloor),
|
|
static_cast<uint8_t>(static_cast<int8_t>(snr)), rx.syncWord,
|
|
});
|
|
if (len) out.insert(out.end(), data, data + len);
|
|
}
|
|
|
|
} // namespace roro::lora
|