diff --git a/README.md b/README.md index 2053d52..14967a4 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 cdd0e89..25d0cd6 100644 --- a/src/serial.zig +++ b/src/serial.zig @@ -989,11 +989,11 @@ pub fn changeControlPins(port: std.Io.File, pins: ControlPins) !void { const SETRTS = 3; if (pins.dtr) |dtr| { - if (EscapeCommFunction(port.handle, if (dtr) SETDTR else CLRDTR) == 0) + if (EscapeCommFunction(port.handle, if (dtr) SETDTR else CLRDTR) == .FALSE) return error.WindowsError; } if (pins.rts) |rts| { - if (EscapeCommFunction(port.handle, if (rts) SETRTS else CLRRTS) == 0) + if (EscapeCommFunction(port.handle, if (rts) SETRTS else CLRRTS) == .FALSE) return error.WindowsError; } }, @@ -1030,12 +1030,72 @@ pub fn changeControlPins(port: std.Io.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.Io.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) == .FALSE) + 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.Io.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) == .FALSE) + 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; @@ -1160,6 +1220,21 @@ test "iterate ports" { } } +fn openTestSerialDeviceFile(io: std.Io) !std.Io.File { + + // if any, these will likely exist on a machine + const tty = switch (builtin.os.tag) { + .windows => "\\\\.\\COM3", + .linux => "/dev/ttyUSB0", + .macos => "/dev/cu.usbmodem101", + else => unreachable, + }; + return std.Io.Dir.openFileAbsolute(io, tty, .{ .mode = .read_write }) catch |err| switch (err) { + error.FileNotFound => return error.SkipZigTest, + else => |e| return e, + }; +} + test "basic configuration test" { const cfg = SerialConfig{ .handshake = .none, @@ -1169,39 +1244,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 = std.Io.Dir.openFileAbsolute(std.testing.io, tty, .{ .mode = .read_write }) catch |err| switch(err) { - error.FileNotFound => return error.SkipZigTest, - else => |e| return e, - }; + const port = try openTestSerialDeviceFile(std.testing.io); defer port.close(std.testing.io); - 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 = std.Io.Dir.openFileAbsolute(std.testing.io, tty, .{ .mode = .read_write }) catch |err| switch(err) { - error.FileNotFound => return error.SkipZigTest, - else => |e| return e, - }; + const port = try openTestSerialDeviceFile(std.testing.io); defer port.close(std.testing.io); - try flushSerialPort(port, .both); try flushSerialPort(port, .input); try flushSerialPort(port, .output); @@ -1211,6 +1261,20 @@ test "change control pins" { _ = changeControlPins; } +test "receive buffer counts" { + const port = try openTestSerialDeviceFile(std.testing.io); + defer port.close(std.testing.io); + try flushSerialPort(port, .input); + try std.testing.expectEqual(0, try receiveBufferCount(port)); +} + +test "transmit buffer counts" { + const port = try openTestSerialDeviceFile(std.testing.io); + defer port.close(std.testing.io); + try flushSerialPort(port, .output); + try std.testing.expectEqual(0, try transmitBufferCount(port)); +} + test "bufPrint tests" { var buf: [32]u8 = undefined;