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
63 lines
2.2 KiB
C++
63 lines
2.2 KiB
C++
#include "services/lora_capture_service.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "loratap.h"
|
|
#include "platform/console.h"
|
|
|
|
namespace roro {
|
|
|
|
std::string LoraCaptureService::start(uint32_t nowMs) {
|
|
if (capturing()) return "";
|
|
if (!radio_.present()) return "No LoRa radio";
|
|
StorageState card = storage_.state();
|
|
if (!card.present) return "No SD card";
|
|
if (!card.capturesAllowed) return "The SD card is full";
|
|
int64_t now = clock_.utcNow();
|
|
if (now < 0) return "Waiting for the time (GNSS or Wi-Fi)";
|
|
path_ = lora::capturePath(now);
|
|
std::vector<uint8_t> header;
|
|
lora::appendPcapHeader(header);
|
|
storage_.appendBytes(path_, std::string(header.begin(), header.end()), true);
|
|
startUtc_ = now;
|
|
startMs_ = nowMs;
|
|
written_ = radio_.received(); // from now on
|
|
packets_ = 0;
|
|
radio_.listen(RadioService::Capture, true);
|
|
console.printf("lora: Capture %s started\n", path_.c_str());
|
|
notify("LoRa Capture started");
|
|
return "";
|
|
}
|
|
|
|
void LoraCaptureService::stop() {
|
|
if (!capturing()) return;
|
|
radio_.listen(RadioService::Capture, false);
|
|
console.printf("lora: Capture %s stopped, %lu packets\n", path_.c_str(), (unsigned long)packets_);
|
|
notify("LoRa Capture stopped: " + std::to_string(packets_) + " packets");
|
|
path_.clear();
|
|
}
|
|
|
|
void LoraCaptureService::tick(uint32_t nowMs) {
|
|
(void)nowMs;
|
|
if (!capturing()) return;
|
|
uint32_t newest = radio_.received();
|
|
for (uint32_t seq = written_ + 1; seq <= newest; ++seq) {
|
|
RadioPacket p;
|
|
if (!radio_.packet(seq, p)) continue; // already left the ring: too many at once
|
|
uint32_t sinceStart = p.ms - startMs_;
|
|
std::vector<uint8_t> record;
|
|
record.reserve(lora::kRecordOverhead + p.len);
|
|
lora::appendRecord(record, static_cast<uint32_t>(startUtc_ + sinceStart / 1000), (sinceStart % 1000) * 1000,
|
|
p.rx, p.data, p.len);
|
|
storage_.appendBytes(path_, std::string(record.begin(), record.end()), true);
|
|
++packets_;
|
|
}
|
|
written_ = newest;
|
|
}
|
|
|
|
void LoraCaptureService::notify(const std::string& text) {
|
|
bus_.publish(Event::withText(EventType::Notification, text.c_str(), static_cast<int32_t>(NotificationLevel::Info)));
|
|
}
|
|
|
|
} // namespace roro
|