From 3d1c5a1f9231c80083c69c2db58480433531c3fc Mon Sep 17 00:00:00 2001 From: Hayden Riddiford Date: Thu, 9 Oct 2025 09:48:14 -0700 Subject: [PATCH] - Added ability to query number of bytes in send/receive buffers --- README.md | 1 + src/serial.zig | 109 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 957b76d..a6ca5b8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Library for configuring and listing serial ports. - Handshake (none, hardware, software) - Byte Size (5, 6, 7, 8) - Flush serial port send/receive buffers +- Query number of bytes in serial port send/receive buffers - List available serial ports - API: supports Windows, Linux and Mac diff --git a/src/serial.zig b/src/serial.zig index e020d78..825a19d 100644 --- a/src/serial.zig +++ b/src/serial.zig @@ -1015,12 +1015,72 @@ pub fn changeControlPins(port: std.fs.File, pins: ControlPins) !void { return error.Unexpected; }, - .macos => {}, - else => @compileError("changeControlPins not implemented for " ++ @tagName(builtin.os.tag)), } } +/// TODO: This struct should probably be added to the Zig standard library at std.os.windows +const COMSTAT = extern struct { + status: packed struct(std.os.windows.DWORD) { + fCtsHold: u1, + fDsrHold: u1, + fRlsdHold: u1, + fXoffHold: u1, + fXoffSent: u1, + fEof: u1, + fTxim: u1, + fReserved: u25, + }, + cbInQue: std.os.windows.DWORD, + cbOutQue: std.os.windows.DWORD, +}; + +extern "kernel32" fn ClearCommError(hFile: std.os.windows.HANDLE, lpErrors: *std.os.windows.DWORD, lpStat: *COMSTAT) std.os.windows.BOOL; + +/// Returns the number of bytes waiting in the receive buffer +pub fn receiveBufferCount(port: std.fs.File) !usize { + switch (builtin.os.tag) { + .windows => { + var ret_error: std.os.windows.DWORD = 0; + var ret_comstat: COMSTAT = std.mem.zeroes(COMSTAT); + if (ClearCommError(port.handle, &ret_error, &ret_comstat) == 0) + return error.Unexpected; + return @intCast(ret_comstat.cbInQue); + }, + .linux => { + // from /usr/include/asm-generic/ioctls.h + const FIONREAD = 0x541B; + var bytes_avail: usize = 0; + if (std.os.linux.ioctl(port.handle, FIONREAD, @intFromPtr(&bytes_avail)) != 0) + return error.Unexpected; + return bytes_avail; + }, + else => @compileError("receiveBufferCount not implemented for " ++ @tagName(builtin.os.tag)), + } +} + +/// Returns the number of bytes waiting in the transmit buffer +pub fn transmitBufferCount(port: std.fs.File) !usize { + switch (builtin.os.tag) { + .windows => { + var ret_error: std.os.windows.DWORD = 0; + var ret_comstat: COMSTAT = std.mem.zeroes(COMSTAT); + if (ClearCommError(port.handle, &ret_error, &ret_comstat) == 0) + return error.Unexpected; + return @intCast(ret_comstat.cbOutQue); + }, + .linux => { + // from /usr/include/asm-generic/ioctls.h + const TIOCOUTQ = 0x5411; + var bytes_avail: usize = 0; + if (std.os.linux.ioctl(port.handle, TIOCOUTQ, @intFromPtr(&bytes_avail)) != 0) + return error.Unexpected; + return bytes_avail; + }, + else => @compileError("transmitBufferCount not implemented for " ++ @tagName(builtin.os.tag)), + } +} + const PURGE_RXABORT = 0x0002; const PURGE_RXCLEAR = 0x0008; const PURGE_TXABORT = 0x0001; @@ -1145,6 +1205,16 @@ test "iterate ports" { } } +fn openTestSerialDeviceFile() !std.fs.File { + // if any, these will likely exist on a machine + return switch (builtin.os.tag) { + .windows => std.fs.cwd().openFile("\\\\.\\COM3", .{ .mode = .read_write }), + .linux => std.fs.cwd().openFile("/dev/ttyUSB0", .{ .mode = .read_write }), + .macos => std.fs.cwd().openFile("/dev/cu.usbmodem101", .{ .mode = .read_write }), + else => unreachable, + }; +} + test "basic configuration test" { const cfg = SerialConfig{ .handshake = .none, @@ -1154,31 +1224,14 @@ test "basic configuration test" { .stop_bits = .one, }; - var tty: []const u8 = undefined; - - switch (builtin.os.tag) { - .windows => tty = "\\\\.\\COM3", - .linux => tty = "/dev/ttyUSB0", - .macos => tty = "/dev/cu.usbmodem101", - else => unreachable, - } - - var port = try std.fs.cwd().openFile(tty, .{ .mode = .read_write }); + var port = try openTestSerialDeviceFile(); defer port.close(); try configureSerialPort(port, cfg); } test "basic flush test" { - var tty: []const u8 = undefined; - // if any, these will likely exist on a machine - switch (builtin.os.tag) { - .windows => tty = "\\\\.\\COM3", - .linux => tty = "/dev/ttyUSB0", - .macos => tty = "/dev/cu.usbmodem101", - else => unreachable, - } - var port = try std.fs.cwd().openFile(tty, .{ .mode = .read_write }); + var port = try openTestSerialDeviceFile(); defer port.close(); try flushSerialPort(port, .both); @@ -1190,6 +1243,20 @@ test "change control pins" { _ = changeControlPins; } +test "receive buffer counts" { + var port = try openTestSerialDeviceFile(); + defer port.close(); + try flushSerialPort(port, .input); + try std.testing.expectEqual(0, try receiveBufferCount(port)); +} + +test "transmit buffer counts" { + var port = try openTestSerialDeviceFile(); + defer port.close(); + try flushSerialPort(port, .output); + try std.testing.expectEqual(0, try transmitBufferCount(port)); +} + test "bufPrint tests" { var buf: [32]u8 = undefined;