263 lines
9.1 KiB
C++
Raw Normal View History

2021-08-16 18:49:00 +02:00
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
2022-07-15 17:17:53 +02:00
#include <gui/style.h>
#include <signal_path/signal_path.h>
2021-08-16 18:49:00 +02:00
SDRPP_MOD_INFO{
2021-08-16 18:49:00 +02:00
/* Name: */ "scanner",
/* Description: */ "Frequency scanner for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
class ScannerModule : public ModuleManager::Instance {
public:
ScannerModule(std::string name) {
this->name = name;
gui::menu.registerEntry(name, menuHandler, this, NULL);
}
~ScannerModule() {
gui::menu.removeEntry(name);
2022-07-15 17:17:53 +02:00
stop();
2021-08-16 18:49:00 +02:00
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
ScannerModule* _this = (ScannerModule*)ctx;
2022-07-15 17:17:53 +02:00
float menuWidth = ImGui::GetContentRegionAvail().x;
if (_this->running) { ImGui::BeginDisabled(); }
ImGui::LeftLabel("Start");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputDouble("##start_freq_scanner", &_this->startFreq, 100.0, 100000.0, "%0.0f")) {
_this->startFreq = round(_this->startFreq);
}
ImGui::LeftLabel("Stop");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputDouble("##stop_freq_scanner", &_this->stopFreq, 100.0, 100000.0, "%0.0f")) {
_this->stopFreq = round(_this->stopFreq);
}
ImGui::LeftLabel("Interval");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputDouble("##interval_scanner", &_this->interval, 100.0, 100000.0, "%0.0f")) {
_this->interval = round(_this->interval);
}
if (_this->running) { ImGui::EndDisabled(); }
ImGui::LeftLabel("Level");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::SliderFloat("##scanner_level", &_this->level, -150.0, 0.0);
if (!_this->running) {
if (ImGui::Button("Start##scanner_start", ImVec2(menuWidth, 0))) {
_this->start();
}
}
else {
if (ImGui::Button("Stop##scanner_start", ImVec2(menuWidth, 0))) {
_this->stop();
}
}
if (ImGui::Button("<<##scanner_start", ImVec2(menuWidth, 0))) {
std::lock_guard<std::mutex> lck(_this->scanMtx);
_this->receiving = false;
_this->scanUp = false;
}
if (ImGui::Button(">>##scanner_start", ImVec2(menuWidth, 0))) {
std::lock_guard<std::mutex> lck(_this->scanMtx);
_this->receiving = false;
_this->scanUp = true;
}
}
void start() {
if (running) { return; }
current = startFreq;
running = true;
workerThread = std::thread(&ScannerModule::worker, this);
}
void stop() {
if (!running) { return; }
running = false;
if (workerThread.joinable()) {
workerThread.join();
}
}
void worker() {
// 10Hz scan loop
while (running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lck(scanMtx);
auto now = std::chrono::high_resolution_clock::now();
// Enforce tuning
tuner::normalTuning(selectedVFO, current);
// Check if we are waiting for a tune
if (tuning) {
spdlog::warn("Tuning");
if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - lastTuneTime)).count() > 250.0) {
tuning = false;
}
continue;
}
// Get FFT data
int dataWidth = 0;
float* data = gui::waterfall.acquireLatestFFT(dataWidth);
if (!data) { continue; }
// Get gather waterfall data
double wfCenter = gui::waterfall.getViewOffset() + gui::waterfall.getCenterFrequency();
double wfWidth = gui::waterfall.getViewBandwidth();
double wfStart = wfCenter - (wfWidth / 2.0);
double wfEnd = wfCenter + (wfWidth / 2.0);
// Gather VFO data
double vfoWidth = sigpath::vfoManager.getBandwidth(selectedVFO);
if (receiving) {
spdlog::warn("Receiving");
float maxLevel = getMaxLevel(data, current, vfoWidth, dataWidth, wfStart, wfWidth);
if (maxLevel >= level) {
lastSignalTime = now;
}
else if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - lastSignalTime)).count() > 1000.0) {
receiving = false;
}
}
else {
spdlog::warn("Seeking signal");
double bottomLimit = INFINITY;
double topLimit = -INFINITY;
// Search for a signal in scan direction
if (tuneIfAvailable(scanUp, bottomLimit, topLimit, wfStart, wfEnd, wfWidth, vfoWidth, data, dataWidth)) {
gui::waterfall.releaseLatestFFT();
continue;
}
// Search for signal in the inverse scan direction
if (tuneIfAvailable(!scanUp, bottomLimit, topLimit, wfStart, wfEnd, wfWidth, vfoWidth, data, dataWidth)) {
gui::waterfall.releaseLatestFFT();
continue;
}
// There is no signal on the visible spectrum, tune in scan direction and retry
if (scanUp) {
current = topLimit + interval;
if (current > stopFreq) { current = startFreq; }
}
else {
current = topLimit - interval;
if (current < startFreq) { current = stopFreq; }
}
// If the new current frequency is outside the visible bandwidth, wait for retune
if (current - (vfoWidth/2.0) < wfStart || current + (vfoWidth/2.0) > wfEnd) {
lastTuneTime = now;
tuning = true;
}
}
// Release FFT Data
gui::waterfall.releaseLatestFFT();
}
}
}
bool tuneIfAvailable(bool scanDir, double& bottomLimit, double& topLimit, double wfStart, double wfEnd, double wfWidth, double vfoWidth, float* data, int dataWidth) {
bool found = false;
double freq = current;
for (freq += scanDir ? interval : -interval;
scanDir ? (freq <= stopFreq) : (freq >= startFreq);
freq += scanDir ? interval : -interval) {
// Check if signal is within bounds
if (freq - (vfoWidth/2.0) < wfStart) { break; }
if (freq + (vfoWidth/2.0) > wfEnd) { break; }
if (freq < bottomLimit) { bottomLimit = freq; }
if (freq > topLimit) { topLimit = freq; }
// Check signal level
float maxLevel = getMaxLevel(data, freq, vfoWidth *0.2, dataWidth, wfStart, wfWidth);
if (maxLevel >= level) {
found = true;
receiving = true;
current = freq;
break;
}
}
return found;
}
float getMaxLevel(float* data, double freq, double width, int dataWidth, double wfStart, double wfWidth) {
double low = freq - (width/2.0);
double high = freq + (width/2.0);
int lowId = std::clamp<int>((low - wfStart) * (double)dataWidth / wfWidth, 0, dataWidth - 1);
int highId = std::clamp<int>((high - wfStart) * (double)dataWidth / wfWidth, 0, dataWidth - 1);
float max = -INFINITY;
for (int i = lowId; i <= highId; i++) {
if (data[i] > max) { max = data[i]; }
}
return max;
2021-08-16 18:49:00 +02:00
}
std::string name;
bool enabled = true;
2022-07-15 17:17:53 +02:00
bool running = false;
std::string selectedVFO = "Radio";
double startFreq = 93300000.0;
double stopFreq = 98700000.0;
double interval = 100000.0;
double current = 88000000.0;
float level = -50.0;
bool receiving = true;
bool tuning = false;
bool scanUp = true;
std::chrono::time_point<std::chrono::high_resolution_clock> lastSignalTime;
std::chrono::time_point<std::chrono::high_resolution_clock> lastTuneTime;
std::thread workerThread;
std::mutex scanMtx;
2021-08-16 18:49:00 +02:00
};
MOD_EXPORT void _INIT_() {
// Nothing here
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new ScannerModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (ScannerModule*)instance;
}
MOD_EXPORT void _END_() {
// Nothing here
}