Public Access
Wi-Fi Tools: scan logging to CSV, plus sort and filter
- lib/wifi (host-tested): scan_log (CSV field quoting, header/row, a once-per-interval throttle) and network_list_view (sort by signal / channel / name, filter open-only / hide-hidden / strong-only) - ScanEntry moved to lib/wifi so both are testable on the PC - Wi-Fi Tools networks view: s sort, o/h/w filters, l toggles logging to /wifi/scans/<date>.csv (header + one row per AP per logged scan, throttled 30 s, needs the clock for timestamps); foreground-only - Wi-Fi scan logs replace probe-request logs as a clean-up category - Serial: cat <path>, and key <char> injects a character Verified on the device: CSV written with header, timestamps, BSSID, channel, RSSI, security and SSID; hidden networks marked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include "network_list_view.h"
|
||||
|
||||
using namespace roro;
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static std::vector<ScanEntry> sample() {
|
||||
return {
|
||||
{"beta", "b", 6, -70, false, "WPA2"},
|
||||
{"alpha", "a", 11, -50, true, "open"},
|
||||
{"", "h", 1, -60, false, "WPA2"}, // hidden
|
||||
{"Gamma", "g", 6, -45, false, "WPA3"},
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> names(const std::vector<ScanEntry>& e, const std::vector<int>& idx) {
|
||||
std::vector<std::string> out;
|
||||
for (int i : idx) out.push_back(e[i].ssid);
|
||||
return out;
|
||||
}
|
||||
|
||||
void test_sort_by_signal_strongest_first() {
|
||||
auto e = sample();
|
||||
auto idx = orderNetworks(e, WifiSort::Signal, {});
|
||||
TEST_ASSERT_EQUAL(4, idx.size());
|
||||
TEST_ASSERT_EQUAL_STRING("Gamma", e[idx[0]].ssid.c_str()); // -45
|
||||
TEST_ASSERT_EQUAL_STRING("alpha", e[idx[1]].ssid.c_str()); // -50
|
||||
TEST_ASSERT_EQUAL_STRING("beta", e[idx[3]].ssid.c_str()); // -70
|
||||
}
|
||||
|
||||
void test_sort_by_channel_then_signal() {
|
||||
auto e = sample();
|
||||
auto idx = orderNetworks(e, WifiSort::Channel, {});
|
||||
TEST_ASSERT_EQUAL(1, e[idx[0]].channel); // channel 1 first
|
||||
TEST_ASSERT_EQUAL(6, e[idx[1]].channel); // then channel 6...
|
||||
TEST_ASSERT_EQUAL_STRING("Gamma", e[idx[1]].ssid.c_str()); // -45 beats -70 on ch 6
|
||||
TEST_ASSERT_EQUAL(11, e[idx[3]].channel);
|
||||
}
|
||||
|
||||
void test_sort_by_name_case_insensitive_hidden_last() {
|
||||
auto e = sample();
|
||||
auto idx = orderNetworks(e, WifiSort::Name, {});
|
||||
auto n = names(e, idx);
|
||||
TEST_ASSERT_EQUAL_STRING("alpha", n[0].c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("beta", n[1].c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("Gamma", n[2].c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("", n[3].c_str()); // hidden last
|
||||
}
|
||||
|
||||
void test_filter_open_only() {
|
||||
auto e = sample();
|
||||
auto idx = orderNetworks(e, WifiSort::Signal, {true, false, -100});
|
||||
TEST_ASSERT_EQUAL(1, idx.size());
|
||||
TEST_ASSERT_EQUAL_STRING("alpha", e[idx[0]].ssid.c_str());
|
||||
}
|
||||
|
||||
void test_filter_hide_hidden_and_weak() {
|
||||
auto e = sample();
|
||||
NetworkFilter f;
|
||||
f.hideHidden = true;
|
||||
f.minRssi = -60;
|
||||
auto idx = orderNetworks(e, WifiSort::Signal, f);
|
||||
// keeps Gamma(-45), alpha(-50); drops beta(-70) and the hidden(-60 is not < -60, but hidden)
|
||||
for (int i : idx) TEST_ASSERT_FALSE(e[i].ssid.empty());
|
||||
TEST_ASSERT_EQUAL(2, idx.size());
|
||||
}
|
||||
|
||||
void test_sort_cycles() {
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(WifiSort::Channel), static_cast<int>(nextSort(WifiSort::Signal)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(WifiSort::Name), static_cast<int>(nextSort(WifiSort::Channel)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(WifiSort::Signal), static_cast<int>(nextSort(WifiSort::Name)));
|
||||
TEST_ASSERT_EQUAL_STRING("signal", sortName(WifiSort::Signal));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_sort_by_signal_strongest_first);
|
||||
RUN_TEST(test_sort_by_channel_then_signal);
|
||||
RUN_TEST(test_sort_by_name_case_insensitive_hidden_last);
|
||||
RUN_TEST(test_filter_open_only);
|
||||
RUN_TEST(test_filter_hide_hidden_and_weak);
|
||||
RUN_TEST(test_sort_cycles);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include "scan_log.h"
|
||||
|
||||
using namespace roro;
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static ScanEntry ap(std::string ssid, std::string bssid, int ch, int rssi, std::string sec) {
|
||||
return {ssid, bssid, ch, rssi, sec == "open", sec};
|
||||
}
|
||||
|
||||
void test_plain_fields_are_unquoted() {
|
||||
TEST_ASSERT_EQUAL_STRING("home", scanlog::csvField("home").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("WPA2", scanlog::csvField("WPA2").c_str());
|
||||
}
|
||||
|
||||
void test_fields_with_commas_or_quotes_are_quoted() {
|
||||
TEST_ASSERT_EQUAL_STRING("\"a,b\"", scanlog::csvField("a,b").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("\"say \"\"hi\"\"\"", scanlog::csvField("say \"hi\"").c_str());
|
||||
}
|
||||
|
||||
void test_header_and_row() {
|
||||
TEST_ASSERT_EQUAL_STRING("time,bssid,channel,rssi,security,ssid", scanlog::header().c_str());
|
||||
auto r = scanlog::row("2026-10-03 20:45:01", ap("home", "aa:bb:cc:dd:ee:ff", 6, -55, "WPA2"));
|
||||
TEST_ASSERT_EQUAL_STRING("2026-10-03 20:45:01,aa:bb:cc:dd:ee:ff,6,-55,WPA2,home", r.c_str());
|
||||
}
|
||||
|
||||
void test_hidden_network_row() {
|
||||
auto r = scanlog::row("t", ap("", "aa:bb:cc:dd:ee:ff", 1, -70, "WPA2"));
|
||||
TEST_ASSERT_TRUE(r.find(",(hidden)") != std::string::npos);
|
||||
}
|
||||
|
||||
void test_ssid_with_a_comma_is_quoted_in_the_row() {
|
||||
auto r = scanlog::row("t", ap("cafe, bar", "aa:bb", 11, -40, "open"));
|
||||
TEST_ASSERT_TRUE(r.find("\"cafe, bar\"") != std::string::npos);
|
||||
}
|
||||
|
||||
void test_throttle_logs_once_per_interval() {
|
||||
ScanLogThrottle t(30000);
|
||||
TEST_ASSERT_TRUE(t.due(0)); // first time
|
||||
t.logged(1000);
|
||||
TEST_ASSERT_FALSE(t.due(30999));
|
||||
TEST_ASSERT_TRUE(t.due(31000));
|
||||
t.logged(31000);
|
||||
TEST_ASSERT_FALSE(t.due(40000));
|
||||
}
|
||||
|
||||
void test_reset_makes_it_due_again() {
|
||||
ScanLogThrottle t(30000);
|
||||
t.logged(1000);
|
||||
TEST_ASSERT_FALSE(t.due(2000));
|
||||
t.reset();
|
||||
TEST_ASSERT_TRUE(t.due(2000));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_plain_fields_are_unquoted);
|
||||
RUN_TEST(test_fields_with_commas_or_quotes_are_quoted);
|
||||
RUN_TEST(test_header_and_row);
|
||||
RUN_TEST(test_hidden_network_row);
|
||||
RUN_TEST(test_ssid_with_a_comma_is_quoted_in_the_row);
|
||||
RUN_TEST(test_throttle_logs_once_per_interval);
|
||||
RUN_TEST(test_reset_makes_it_due_again);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user