add a rudimentary "add from device" window

This commit is contained in:
Rokas Puzonas 2025-02-05 00:03:23 +02:00
parent a87a9c104e
commit 65ad8d7786
4 changed files with 267 additions and 53 deletions

View File

@ -3,3 +3,7 @@
```shell
zig build run
```
## TODO
* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail.

View File

@ -15,6 +15,8 @@ const clamp = std.math.clamp;
const App = @This();
const max_channels = 64;
const Channel = struct {
view_cache: Graph.Cache = .{},
view_rect: Graph.ViewOptions,
@ -31,7 +33,7 @@ const Channel = struct {
allocator: std.mem.Allocator,
ui: UI,
channels: std.BoundedArray(Channel, 64) = .{},
channels: std.BoundedArray(Channel, max_channels) = .{},
ni_daq: NIDaq,
shown_window: enum {
@ -39,6 +41,11 @@ shown_window: enum {
add_from_device
} = .channels,
device_filter: NIDaq.BoundedDeviceName = .{},
show_voltage_analog_inputs: bool = true,
show_voltage_analog_outputs: bool = true,
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
pub fn init(allocator: std.mem.Allocator) !App {
return App{
.allocator = allocator,
@ -51,7 +58,7 @@ pub fn init(allocator: std.mem.Allocator) !App {
.max_counter_inputs = 8,
.max_analog_input_voltage_ranges = 4,
.max_analog_output_voltage_ranges = 4
}),
})
};
}
@ -64,6 +71,11 @@ pub fn deinit(self: *App) void {
channel.view_cache.deinit();
}
for (self.selected_channels.constSlice()) |channel| {
self.allocator.free(channel);
}
self.selected_channels.len = 0;
self.ui.deinit();
}
@ -280,9 +292,182 @@ fn showChannelsWindow(self: *App) void {
}
}
fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize {
for (0.., haystack) |i, item| {
if (std.mem.eql(u8, item, needle)) {
return i;
}
}
return null;
}
fn showAddFromDeviceWindow(self: *App) !void {
const names = try self.ni_daq.listDeviceNames();
_ = names;
const window = self.ui.newBoxFromString("Device window");
window.size.x = UI.Size.percent(1, 0);
window.size.y = UI.Size.percent(1, 0);
window.layout_axis = .X;
self.ui.pushParent(window);
defer self.ui.popParent();
{
const filters_box = self.ui.newBoxFromString("Filters box");
filters_box.size.x = UI.Size.percent(0.5, 1);
filters_box.size.y = UI.Size.percent(1, 0);
filters_box.layout_axis = .Y;
self.ui.pushParent(filters_box);
defer self.ui.popParent();
for (try self.ni_daq.listDeviceNames()) |device| {
const device_box = self.ui.newBoxFromString(device);
device_box.flags.insert(.clickable);
device_box.size.x = UI.Size.text(2, 1);
device_box.size.y = UI.Size.text(2, 1);
device_box.setText(.text, device);
const signal = self.ui.signalFromBox(device_box);
if (signal.clicked()) {
self.device_filter = try NIDaq.BoundedDeviceName.fromSlice(device);
}
}
{
const toggle_inputs_box = self.ui.newBoxFromString("Toggle inputs");
toggle_inputs_box.flags.insert(.clickable);
toggle_inputs_box.size.x = UI.Size.text(2, 1);
toggle_inputs_box.size.y = UI.Size.text(2, 1);
toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs");
const signal = self.ui.signalFromBox(toggle_inputs_box);
if (signal.clicked()) {
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
}
}
{
const toggle_outputs_box = self.ui.newBoxFromString("Toggle outputs");
toggle_outputs_box.flags.insert(.clickable);
toggle_outputs_box.size.x = UI.Size.text(2, 1);
toggle_outputs_box.size.y = UI.Size.text(2, 1);
toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs");
const signal = self.ui.signalFromBox(toggle_outputs_box);
if (signal.clicked()) {
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
}
}
{
const add_button = self.ui.newBoxFromString("Add");
add_button.flags.insert(.clickable);
add_button.size.x = UI.Size.text(2, 1);
add_button.size.y = UI.Size.text(2, 1);
add_button.setText(.text, "Add selected");
const signal = self.ui.signalFromBox(add_button);
if (signal.clicked()) {
const selected_devices = self.selected_channels.constSlice();
std.debug.print("{s}\n", .{selected_devices});
for (self.selected_channels.constSlice()) |channel| {
self.allocator.free(channel);
}
self.selected_channels.len = 0;
self.shown_window = .channels;
}
}
}
{
const channels_box = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels list"));
defer self.ui.popScrollbar();
channels_box.layout_axis = .Y;
channels_box.size.x = UI.Size.percent(1, 0);
var devices: []const [:0]const u8 = &.{};
if (self.device_filter.len > 0) {
devices = &.{
self.device_filter.buffer[0..self.device_filter.len :0]
};
} else {
devices = try self.ni_daq.listDeviceNames();
}
for (devices) |device| {
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
if (self.show_voltage_analog_inputs) {
if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device);
}
}
var ao_physical_channels: []const [:0]const u8 = &.{};
if (self.show_voltage_analog_outputs) {
if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
}
}
inline for (.{ ai_voltage_physical_channels, ao_physical_channels }) |channels| {
for (channels) |channel| {
const selected_channels_slice = self.selected_channels.constSlice();
const channel_box = self.ui.newBoxFromString(channel);
channel_box.flags.insert(.clickable);
channel_box.size.x = UI.Size.text(1, 1);
channel_box.size.y = UI.Size.text(0.5, 1);
channel_box.setText(.text, channel);
if (findChannelIndexByName(selected_channels_slice, channel) != null) {
channel_box.background = srcery.xgray3;
}
const signal = self.ui.signalFromBox(channel_box);
if (signal.clicked()) {
if (findChannelIndexByName(selected_channels_slice, channel)) |index| {
self.allocator.free(self.selected_channels.swapRemove(index));
} else {
self.selected_channels.appendAssumeCapacity(try self.allocator.dupeZ(u8, channel));
}
}
}
}
}
}
// if (self.selected_device.len > 0) {
// const device: [:0]u8 = self.selected_device.buffer[0..self.selected_device.len :0];
// var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
// if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
// ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device);
// }
// var ao_physical_channels: []const [:0]const u8 = &.{};
// if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
// ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
// }
// const channels_box = self.ui.newBoxFromString("Channel names");
// channels_box.size.x = UI.Size.percent(0.5, 0);
// channels_box.size.y = UI.Size.percent(1, 0);
// channels_box.layout_axis = .Y;
// self.ui.pushParent(channels_box);
// defer self.ui.popParent();
// for (ai_voltage_physical_channels) |channel_name| {
// const channel_box = self.ui.newBoxFromString(channel_name);
// channel_box.flags.insert(.clickable);
// channel_box.size.x = UI.Size.text(1, 0);
// channel_box.size.y = UI.Size.text(1, 0);
// channel_box.setText(.text, channel_name);
// const signal = self.ui.signalFromBox(channel_box);
// if (signal.clicked()) {
// self.selected_device = try NIDaq.BoundedDeviceName.fromSlice(device);
// }
// }
// }
}
fn showToolbar(self: *App) void {
@ -305,7 +490,7 @@ fn showToolbar(self: *App) void {
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from file", .text);
box.setText(.text, "Add from file",);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
@ -329,7 +514,7 @@ fn showToolbar(self: *App) void {
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from device", .text);
box.setText(.text, "Add from device");
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {

View File

@ -11,7 +11,16 @@ const log = std.log.scoped(.ni_daq);
const max_device_name_size = 255;
const max_task_name_size = 255;
pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size);
const max_channel_name_size = count: {
var count: u32 = 0;
count += max_device_name_size;
count += 1; // '/'
count += 2; // 'ai' or 'ao' or 'co' or 'ci'
count += 3; // '0' -> '999', I can't imagine this counter being over 1000. What device has over 1000 channels????
break :count count;
};
pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size + 1); // +1 for null byte
const StringArrayListUnmanaged = std.ArrayListUnmanaged([:0]const u8);
const NIDaq = @This();
@ -276,17 +285,7 @@ const DeviceBuffers = struct {
array_list: StringArrayListUnmanaged,
fn init(allocator: std.mem.Allocator, capacity: u32) !ChannelNames {
const max_channel_name_size = count: {
var count: u32 = 0;
count += max_device_name_size;
count += 1; // '/'
count += 2; // 'ai' or 'ao' or 'co' or 'ci'
count += std.math.log10_int(capacity) + 1;
break :count count;
};
const buffer_size = capacity * (max_channel_name_size + 2);
const buffer_size = capacity * (max_channel_name_size + 2); // +2 for ', ' separator
const buffer = try allocator.alloc(u8, buffer_size);
errdefer allocator.free(buffer);
@ -434,13 +433,7 @@ pub fn logDAQmxError(error_code: i32) void {
var msg: [512:0]u8 = .{ 0 } ** 512;
if (c.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) {
if (error_code < 0) {
log.err("DAQmx ({}): {s}", .{error_code, msg});
} else if (!std.mem.startsWith(u8, &msg, "Error code could not be found.")) {
// Ignore positive error codes if it could not be found.
// This commonly happens when trying to preallocate bytes for buffer and it returns a positive number.
log.warn("DAQmx ({}): {s}", .{error_code, msg});
}
log.err("DAQmx ({}): {s}", .{error_code, msg});
} else {
log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code});
}
@ -454,6 +447,14 @@ pub fn checkDAQmxError(error_code: i32, err: anyerror) !void {
}
}
pub fn checkDAQmxErrorIgnoreWarnings(error_code: i32, err: anyerror) !void {
if (error_code > 0) {
return;
}
try checkDAQmxError(error_code, err);
}
fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void {
const count = std.mem.count(u8, buffer, ",") + 1;
if (count > array_list.capacity) {
@ -504,6 +505,7 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 {
self.clearAllDeviceBuffers();
const required_size = c.DAQmxGetSysDevNames(null, 0);
try checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames);
if (required_size == 0) {
return self.device_names.items;
}
@ -560,7 +562,7 @@ fn listDevicePhysicalChannels(
array_list.clearRetainingCapacity();
const required_size = getPhysicalChannels(device, null, 0);
try checkDAQmxError(required_size, error.GetPhysicalChannels);
try checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels);
if (required_size == 0) {
return array_list.items;
}
@ -710,7 +712,7 @@ fn listDeviceVoltageRanges(
voltage_ranges.clearRetainingCapacity();
const count = getVoltageRanges(device, null, 0);
try checkDAQmxError(count, error.GetVoltageRanges);
try checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges);
if (count == 0) {
return voltage_ranges.items;
}
@ -771,7 +773,7 @@ pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasur
_ = self;
const count = c.DAQmxGetDevAISupportedMeasTypes(device, null, 0);
try checkDAQmxError(count, error.DAQmxGetDevAISupportedMeasTypes);
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes);
assert(count <= result.buffer.len);
try checkDAQmxError(
@ -794,7 +796,7 @@ pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeL
_ = self;
const count = c.DAQmxGetDevAOSupportedOutputTypes(device, null, 0);
try checkDAQmxError(count, error.DAQmxGetDevAOSupportedOutputTypes);
try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes);
assert(count <= result.buffer.len);
try checkDAQmxError(

View File

@ -287,13 +287,20 @@ pub const Box = struct {
};
}
pub fn setText(self: *Box, text: []const u8, font: Assets.FontId) void {
pub fn setText(self: *Box, font: Assets.FontId, text: []const u8) void {
self.text = .{
.content = self.allocator.dupe(u8, text) catch return,
.font = font
};
}
pub fn setAllocText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
self.text = .{
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
.font = font
};
}
pub fn setFixedX(self: *Box, x: f32) void {
self.flags.insert(.fixed_x);
self.fixed_rect.x = x;
@ -452,6 +459,19 @@ pub fn begin(self: *UI) void {
);
}
{
var i: usize = 0;
while (i < self.boxes.len) {
const box = &self.boxes.buffer[i];
if (box.last_used_frame != self.frame_index) {
_ = self.boxes.swapRemove(i);
} else {
i += 1;
}
}
}
self.frame_index += 1;
_ = self.frameArena().reset(.retain_capacity);
self.parent_index_stack.len = 0;
@ -489,19 +509,6 @@ pub fn end(self: *UI) void {
self.popParent();
assert(self.parent_index_stack.len == 0);
{
var i: usize = 0;
while (i < self.boxes.len) {
const box = &self.boxes.buffer[i];
if (box.last_used_frame != self.frame_index) {
_ = self.boxes.swapRemove(i);
continue;
}
i += 1;
}
}
{
var active_box_flags = self.getActiveBoxFlags();
var hover_box_flags: Box.Flags = .{};
@ -921,12 +928,13 @@ pub fn newBox(self: *UI, key: Key) *Box {
var box: *Box = undefined;
var box_index: BoxIndex = undefined;
var persistent: Box.Persistent = .{};
if (self.findBoxByKey(key)) |found_box| {
if (self.findBoxIndexByKey(key)) |found_box_index| {
const found_box = &self.boxes.buffer[found_box_index];
assert(found_box.last_used_frame < self.frame_index);
persistent = found_box.persistent;
box = found_box;
box_index = found_box.index;
box_index = found_box_index;
} else {
box = self.boxes.addOneAssumeCapacity();
box_index = self.boxes.len - 1;
@ -941,8 +949,9 @@ pub fn newBox(self: *UI, key: Key) *Box {
.index = box_index
};
if (self.getParent()) |parent| {
box.parent_index = parent.index;
if (self.getParentIndex()) |parent_index| {
const parent = &self.boxes.buffer[parent_index];
box.parent_index = parent_index;
if (parent.last_child_index) |last_child_index| {
const last_child = &self.boxes.buffer[last_child_index];
@ -958,15 +967,22 @@ pub fn newBox(self: *UI, key: Key) *Box {
return box;
}
fn findBoxByKey(self: *UI, key: Key) ?*Box {
for (self.boxes.slice()) |*box| {
fn findBoxIndexByKey(self: *UI, key: Key) ?BoxIndex {
for (0.., self.boxes.slice()) |index, *box| {
if (box.key.eql(key)) {
return box;
return @intCast(index);
}
}
return null;
}
fn findBoxByKey(self: *UI, key: Key) ?*Box {
if (self.findBoxIndexByKey(key)) |box_index| {
return &self.boxes.buffer[box_index];
}
return null;
}
pub fn signalFromBox(self: *UI, box: *Box) Signal {
var result = Signal{};
@ -1058,11 +1074,18 @@ fn isBoxActive(self: *UI, key: Key) bool {
return false;
}
pub fn getParent(self: *UI) ?*Box {
fn getParentIndex(self: *UI) ?BoxIndex {
const parent_stack: []BoxIndex = self.parent_index_stack.slice();
if (parent_stack.len > 0) {
const parent_index = parent_stack[parent_stack.len - 1];
return parent_stack[parent_stack.len - 1];
} else {
return null;
}
}
pub fn getParent(self: *UI) ?*Box {
if (self.getParentIndex()) |parent_index| {
return &self.boxes.buffer[parent_index];
} else {
return null;