Feature/source plugins #1
@@ -11,6 +11,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace udpscope {
|
namespace udpscope {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -82,6 +83,11 @@ struct Form {
|
|||||||
ChangeCb changed = std::make_shared<std::function<void()>>();
|
ChangeCb changed = std::make_shared<std::function<void()>>();
|
||||||
bool updating = false;
|
bool updating = false;
|
||||||
bool alive = true; ///< cleared when the chooser destroys the rows; handlers then touch nothing
|
bool alive = true; ///< cleared when the chooser destroys the rows; handlers then touch nothing
|
||||||
|
GtkWidget* scan_row = nullptr; ///< the "Scan network" expander; the results are its rows
|
||||||
|
std::vector<GtkWidget*> results; ///< the result rows of the current scan
|
||||||
|
std::unique_ptr<Scan> scan; ///< non-null while a scan is running
|
||||||
|
guint timer = 0; ///< the GLib source polling `scan`, 0 when none
|
||||||
|
int scan_ms = 0; ///< time spent in the running scan
|
||||||
|
|
||||||
bool diff() const { return sel_of(mode) == 1; }
|
bool diff() const { return sel_of(mode) == 1; }
|
||||||
int enabled_ai() const {
|
int enabled_ai() const {
|
||||||
@@ -164,6 +170,24 @@ struct Form {
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A result click: puts one way of naming the board in and the other two fields out, under the
|
||||||
|
/// `updating` guard so the dialog is told once.
|
||||||
|
void fill(const std::string& addr, const std::string& mac_text) {
|
||||||
|
if (!alive) return;
|
||||||
|
updating = true;
|
||||||
|
gtk_editable_set_text(GTK_EDITABLE(address), addr.c_str());
|
||||||
|
gtk_editable_set_text(GTK_EDITABLE(mac), mac_text.c_str());
|
||||||
|
gtk_editable_set_text(GTK_EDITABLE(subnet), "");
|
||||||
|
updating = false;
|
||||||
|
if (changed && *changed) (*changed)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_scan() {
|
||||||
|
if (timer != 0) g_source_remove(timer);
|
||||||
|
timer = 0;
|
||||||
|
scan.reset();
|
||||||
|
}
|
||||||
|
|
||||||
bool valid() const { return enabled_ai() > 0 && E1608Settings::from(read()).problem().empty(); }
|
bool valid() const { return enabled_ai() > 0 && E1608Settings::from(read()).problem().empty(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,7 +209,11 @@ void on_edited(GtkEditable*, gpointer user) {
|
|||||||
void free_form(gpointer d, GClosure*) { delete static_cast<FormPtr*>(d); }
|
void free_form(gpointer d, GClosure*) { delete static_cast<FormPtr*>(d); }
|
||||||
|
|
||||||
/// "destroy" on a row group: the siblings the handlers reach by raw pointer may already be gone.
|
/// "destroy" on a row group: the siblings the handlers reach by raw pointer may already be gone.
|
||||||
void on_form_destroy(GtkWidget*, gpointer user) { (**static_cast<FormPtr*>(user)).alive = false; }
|
void on_form_destroy(GtkWidget*, gpointer user) {
|
||||||
|
Form& f = **static_cast<FormPtr*>(user);
|
||||||
|
f.alive = false;
|
||||||
|
f.stop_scan(); // the poll must not outlive the rows it writes to
|
||||||
|
}
|
||||||
|
|
||||||
void hook_destroy(GtkWidget* w, const FormPtr& f) {
|
void hook_destroy(GtkWidget* w, const FormPtr& f) {
|
||||||
g_signal_connect_data(w, "destroy", G_CALLBACK(on_form_destroy), new FormPtr(f), free_form,
|
g_signal_connect_data(w, "destroy", G_CALLBACK(on_form_destroy), new FormPtr(f), free_form,
|
||||||
@@ -202,6 +230,96 @@ void hook_entry(GtkWidget* w, const FormPtr& f) {
|
|||||||
static_cast<GConnectFlags>(0));
|
static_cast<GConnectFlags>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr int kScanMs = 1500; ///< how long a scan listens
|
||||||
|
constexpr int kTickMs = 50; ///< how often it drains the sockets
|
||||||
|
|
||||||
|
/// A result row's two ways to name the board, with the form they fill.
|
||||||
|
struct Pick {
|
||||||
|
FormPtr form;
|
||||||
|
std::string ip, mac;
|
||||||
|
};
|
||||||
|
|
||||||
|
void free_pick(gpointer d, GClosure*) { delete static_cast<Pick*>(d); }
|
||||||
|
|
||||||
|
void on_pick_address(AdwActionRow*, gpointer user) {
|
||||||
|
Pick& p = *static_cast<Pick*>(user);
|
||||||
|
p.form->fill(p.ip, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_pick_mac(GtkButton*, gpointer user) {
|
||||||
|
Pick& p = *static_cast<Pick*>(user);
|
||||||
|
p.form->fill("", p.mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_result(const FormPtr& f, const DeviceInfo& d) {
|
||||||
|
const std::string mac = format_mac(d.mac);
|
||||||
|
GtkWidget* row = adw_action_row_new();
|
||||||
|
gtk_widget_set_name(row, "e1608-result");
|
||||||
|
adw_preferences_row_set_title(ADW_PREFERENCES_ROW(row), d.ip.c_str());
|
||||||
|
adw_action_row_set_subtitle(ADW_ACTION_ROW(row), mac.c_str());
|
||||||
|
gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(row), TRUE);
|
||||||
|
GtkWidget* use_mac = gtk_button_new_with_label("Use MAC");
|
||||||
|
gtk_widget_set_name(use_mac, "e1608-use-mac");
|
||||||
|
gtk_widget_set_valign(use_mac, GTK_ALIGN_CENTER);
|
||||||
|
adw_action_row_add_suffix(ADW_ACTION_ROW(row), use_mac);
|
||||||
|
g_signal_connect_data(row, "activated", G_CALLBACK(on_pick_address),
|
||||||
|
new Pick{f, d.ip, mac}, free_pick, static_cast<GConnectFlags>(0));
|
||||||
|
g_signal_connect_data(use_mac, "clicked", G_CALLBACK(on_pick_mac), new Pick{f, d.ip, mac},
|
||||||
|
free_pick, static_cast<GConnectFlags>(0));
|
||||||
|
adw_expander_row_add_row(ADW_EXPANDER_ROW(f->scan_row), row);
|
||||||
|
adw_expander_row_set_expanded(ADW_EXPANDER_ROW(f->scan_row), TRUE);
|
||||||
|
f->results.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_scan_status(Form& f, const std::string& text) {
|
||||||
|
adw_expander_row_set_subtitle(ADW_EXPANDER_ROW(f.scan_row), text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 50 ms poll of the running scan. The rows appear as boards answer; at `kScanMs` the status is
|
||||||
|
/// settled and the scan (its sockets) is released.
|
||||||
|
gboolean on_scan_tick(gpointer user) {
|
||||||
|
const FormPtr& fp = *static_cast<FormPtr*>(user);
|
||||||
|
Form& f = *fp;
|
||||||
|
if (!f.alive || !f.scan) {
|
||||||
|
f.timer = 0;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
const std::vector<DeviceInfo> found = f.scan->poll();
|
||||||
|
while (f.results.size() < found.size()) add_result(fp, found[f.results.size()]);
|
||||||
|
f.scan_ms += kTickMs;
|
||||||
|
if (f.scan_ms < kScanMs) return G_SOURCE_CONTINUE;
|
||||||
|
if (found.empty())
|
||||||
|
set_scan_status(f, "No board found");
|
||||||
|
else if (found.size() == 1)
|
||||||
|
set_scan_status(f, "1 board found");
|
||||||
|
else
|
||||||
|
set_scan_status(f, std::to_string(found.size()) + " boards found");
|
||||||
|
f.scan.reset();
|
||||||
|
f.timer = 0;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_form_ptr(gpointer d) { delete static_cast<FormPtr*>(d); }
|
||||||
|
|
||||||
|
/// A new scan on the Port field's value: the previous results go, a running scan is replaced.
|
||||||
|
void start_scan(const FormPtr& fp) {
|
||||||
|
Form& f = *fp;
|
||||||
|
if (!f.alive) return;
|
||||||
|
f.stop_scan();
|
||||||
|
for (GtkWidget* r : f.results) adw_expander_row_remove(ADW_EXPANDER_ROW(f.scan_row), r);
|
||||||
|
f.results.clear();
|
||||||
|
adw_expander_row_set_expanded(ADW_EXPANDER_ROW(f.scan_row), FALSE);
|
||||||
|
f.scan = std::make_unique<Scan>(static_cast<int>(value_of(f.port)));
|
||||||
|
f.scan_ms = 0;
|
||||||
|
set_scan_status(f, "Scanning...");
|
||||||
|
f.timer = g_timeout_add_full(G_PRIORITY_DEFAULT, kTickMs, on_scan_tick, new FormPtr(fp),
|
||||||
|
free_form_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_scan_clicked(GtkButton*, gpointer user) {
|
||||||
|
start_scan(*static_cast<FormPtr*>(user));
|
||||||
|
}
|
||||||
|
|
||||||
class E1608Kind final : public SourceKind {
|
class E1608Kind final : public SourceKind {
|
||||||
public:
|
public:
|
||||||
std::string id() const override { return "e1608"; }
|
std::string id() const override { return "e1608"; }
|
||||||
@@ -253,8 +371,18 @@ class E1608Kind final : public SourceKind {
|
|||||||
f->ao_init[0] = spin_row("Initial DAC 0 (V)", -10, 10, 0.1, 0, 3);
|
f->ao_init[0] = spin_row("Initial DAC 0 (V)", -10, 10, 0.1, 0, 3);
|
||||||
f->ao_init[1] = spin_row("Initial DAC 1 (V)", -10, 10, 0.1, 0, 3);
|
f->ao_init[1] = spin_row("Initial DAC 1 (V)", -10, 10, 0.1, 0, 3);
|
||||||
|
|
||||||
GtkWidget* conn = expander("Connection", {f->address, f->mac, f->subnet, f->port, f->code,
|
f->scan_row = adw_expander_row_new();
|
||||||
f->timeout});
|
gtk_widget_set_name(f->scan_row, "e1608-scan-row");
|
||||||
|
adw_preferences_row_set_title(ADW_PREFERENCES_ROW(f->scan_row), "Scan network");
|
||||||
|
GtkWidget* scan_button = gtk_button_new_with_label("Scan");
|
||||||
|
gtk_widget_set_name(scan_button, "e1608-scan");
|
||||||
|
gtk_widget_set_valign(scan_button, GTK_ALIGN_CENTER);
|
||||||
|
adw_expander_row_add_suffix(ADW_EXPANDER_ROW(f->scan_row), scan_button);
|
||||||
|
g_signal_connect_data(scan_button, "clicked", G_CALLBACK(on_scan_clicked), new FormPtr(f),
|
||||||
|
free_form, static_cast<GConnectFlags>(0));
|
||||||
|
|
||||||
|
GtkWidget* conn = expander("Connection", {f->scan_row, f->address, f->mac, f->subnet, f->port,
|
||||||
|
f->code, f->timeout});
|
||||||
GtkWidget* acq = expander("Acquisition", {f->rate, f->ext_clock, f->trigger, f->auto_restart,
|
GtkWidget* acq = expander("Acquisition", {f->rate, f->ext_clock, f->trigger, f->auto_restart,
|
||||||
f->calibrate, f->poll_rate});
|
f->calibrate, f->poll_rate});
|
||||||
GtkWidget* ai = expander("Analog inputs", {f->mode, f->ai[0], f->ai[1], f->ai[2], f->ai[3],
|
GtkWidget* ai = expander("Analog inputs", {f->mode, f->ai[0], f->ai[1], f->ai[2], f->ai[3],
|
||||||
@@ -283,7 +411,8 @@ class E1608Kind final : public SourceKind {
|
|||||||
ConfigForm out;
|
ConfigForm out;
|
||||||
out.rows = {conn, acq, ai, dio};
|
out.rows = {conn, acq, ai, dio};
|
||||||
out.description =
|
out.description =
|
||||||
"Give an address, or a MAC address to look up (optionally with a subnet such as 192.168.1)";
|
"Give an address, or a MAC address to look up (optionally with a subnet such as 192.168.1), "
|
||||||
|
"or scan the network for boards";
|
||||||
out.focus = f->address;
|
out.focus = f->address;
|
||||||
out.write = [f](const SourceConfig& c) { f->write(c); };
|
out.write = [f](const SourceConfig& c) { f->write(c); };
|
||||||
out.read = [f]() { return f->read(); };
|
out.read = [f]() { return f->read(); };
|
||||||
|
|||||||
Reference in new issue
Block a user