// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! A serial port, through zig-serial. //! //! The far end of a serial line has no idea how big the window is and no way //! to be told, so `resize` does nothing; full-screen programs on the far end //! are on their own, as they are with any serial terminal. const std = @import("std"); const serial = @import("serial"); const backend = @import("Backend.zig"); const Serial = @This(); port: serial.Port, /// For `describe`. Owned. path: []const u8, settings: serial.Settings, gpa: std.mem.Allocator, pub const Options = struct { path: []const u8, settings: serial.Settings = .{}, }; pub fn open(gpa: std.mem.Allocator, options: Options) (serial.OpenError || std.mem.Allocator.Error)!Serial { const path = try gpa.dupe(u8, options.path); errdefer gpa.free(path); var port = try serial.Port.open(options.path, options.settings, .{}); errdefer port.close(); // What the driver chose, which is what the title should say. const actual = port.current() catch options.settings; return .{ .port = port, .path = path, .settings = actual, .gpa = gpa }; } /// Windows has nothing to wait on for a port opened without overlapped /// IO, so it is looked at this often instead. pub const poll_interval_ms = 10; pub fn handle(s: *const Serial) backend.Handle { return s.port.handle; } pub fn waitHandles(s: *const Serial, buf: *[backend.max_wait_handles]backend.Handle) []const backend.Handle { _ = s; return buf[0..0]; } pub fn read(s: *Serial, buf: []u8) backend.ReadError!usize { return s.port.read(buf) catch |err| switch (err) { error.WouldBlock => error.WouldBlock, // The device went away; a USB adapter pulled out reads as this. error.InputOutput => 0, error.Unexpected => error.Unexpected, }; } pub fn write(s: *Serial, bytes: []const u8) backend.WriteError!usize { return s.port.write(bytes) catch |err| switch (err) { error.WouldBlock => error.WouldBlock, error.InputOutput => error.Closed, error.Unexpected => error.Unexpected, }; } pub fn resize(s: *Serial, size: backend.Size) void { _ = s; _ = size; } /// `/dev/ttyUSB0 115200 8N1`, the way every serial program has said it. pub fn describe(s: *const Serial, w: *std.Io.Writer) std.Io.Writer.Error!void { const parity: u8 = switch (s.settings.parity) { .none => 'N', .even => 'E', .odd => 'O', .mark => 'M', .space => 'S', }; const stop: []const u8 = switch (s.settings.stop_bits) { .one => "1", .one_and_half => "1.5", .two => "2", }; try w.print("{s} {d} {d}{c}{s}", .{ s.path, s.settings.baud, @backingInt(s.settings.data_bits), parity, stop, }); } pub fn deinit(s: *Serial) void { s.port.close(); s.gpa.free(s.path); } test "describe says rate and framing" { var s: Serial = .{ .port = undefined, .path = "/dev/ttyS0", .settings = .{ .baud = 9600, .data_bits = .seven, .parity = .even, .stop_bits = .two }, .gpa = std.testing.allocator, }; var buf: [64]u8 = undefined; var w: std.Io.Writer = .fixed(&buf); try s.describe(&w); try std.testing.expectEqualStrings("/dev/ttyS0 9600 7E2", w.buffered()); }