Documentation
¶
Overview ¶
Package serial opens and configures a macOS serial port with no cgo.
It exists because a USB device's interesting protocol is not always on a HID interface or on endpoint 0. A composite device that publishes a CDC-ACM function gets a /dev/cu.* and /dev/tty.* pair from Apple's AppleUSBACMData driver, and that pair is a channel the sibling hid and usb packages in this module cannot reach: the kernel driver owns the bulk pipes, so the only way in is the tty.
cu versus tty, which is the whole trap ¶
macOS publishes two nodes for one port.
- /dev/tty.NAME is the dial-IN node. Opening it blocks until the device asserts carrier detect, and it raises DTR. A USB CDC device that never drives DCD will hang an open forever unless the caller passes O_NONBLOCK, which Open always does, and then sets CLOCAL.
- /dev/cu.NAME is the call-OUT node. It opens immediately and does not wait for carrier -- and it does not raise DTR either.
A device whose firmware only starts talking once DTR is asserted is therefore mute on /dev/cu.* and alive on /dev/tty.*, with no error either way. Port.SetLines asserts the lines explicitly so the question can be asked of both nodes rather than guessed.
What an accepted write proves ¶
Nothing, on its own. The lesson the sibling packages record applies here unchanged: write(2) to a tty returns the byte count as soon as the line discipline has queued the bytes, whether or not the device on the other end understood a single one of them. Only bytes coming back are evidence, which is why Loopback exists -- a probe that has not proved its reader on a known-good port has not proved that silence means anything.
Index ¶
- Constants
- Variables
- func BaseName(path string) string
- func Callout(base string) string
- func Dialin(base string) string
- func Drain(r deadlineReader, buf []byte, until time.Time) (int, error)
- func List() ([]string, error)
- func Loopback() (*os.File, string, error)
- type Config
- type ErrBadConfig
- type Lines
- type Parity
- type Port
- func (p *Port) Close() error
- func (p *Port) Config() Config
- func (p *Port) Configure(cfg Config) error
- func (p *Port) Flush() error
- func (p *Port) Lines() (Lines, error)
- func (p *Port) Name() string
- func (p *Port) Read(b []byte) (int, error)
- func (p *Port) SetLines(set, clear Lines) error
- func (p *Port) SetReadDeadline(t time.Time) error
- func (p *Port) Termios() (Termios, error)
- func (p *Port) Write(b []byte) (int, error)
- type Termios
Constants ¶
const ( // CalloutPrefix names the dial-out node, which opens without waiting for // carrier and leaves DTR low. CalloutPrefix = "cu." // DialinPrefix names the dial-in node, which raises DTR and waits for // carrier. DialinPrefix = "tty." )
Prefixes of the two node families macOS publishes for one port.
const ( VMIN = 16 VTIME = 17 )
Indices into Termios.Cc.
const ( IGNBRK = 0x00000001 BRKINT = 0x00000002 IGNPAR = 0x00000004 PARMRK = 0x00000008 INPCK = 0x00000010 ISTRIP = 0x00000020 INLCR = 0x00000040 IGNCR = 0x00000080 ICRNL = 0x00000100 IXON = 0x00000200 IXOFF = 0x00000400 IXANY = 0x00000800 )
Input flags, termios c_iflag.
const ( OPOST = 0x00000001 ONLCR = 0x00000002 )
Output flags, termios c_oflag.
const ( CSIZE = 0x00000300 CS5 = 0x00000000 CS6 = 0x00000100 CS7 = 0x00000200 CS8 = 0x00000300 CSTOPB = 0x00000400 CREAD = 0x00000800 PARENB = 0x00001000 PARODD = 0x00002000 HUPCL = 0x00004000 CLOCAL = 0x00008000 // CRTSCTS is CCTS_OFLOW|CRTS_IFLOW: BSD splits hardware flow control into // the two directions and defines the joint name as their union. CCTSOFLOW = 0x00010000 CRTSIFLOW = 0x00020000 CRTSCTS = CCTSOFLOW | CRTSIFLOW )
Control flags, termios c_cflag.
const ( ECHOE = 0x00000002 ECHOK = 0x00000004 ECHO = 0x00000008 ECHONL = 0x00000010 ISIG = 0x00000080 ICANON = 0x00000100 IEXTEN = 0x00000400 )
Local flags, termios c_lflag.
const DevDir = "/dev"
DevDir is where macOS publishes tty nodes.
const NCCS = 20
NCCS is the number of control characters in a BSD termios.
Variables ¶
var ErrClosed = errors.New("serial: port is closed")
ErrClosed is returned by a method called after Port.Close.
var ErrUnsupported = errors.New("serial: unsupported on this platform")
ErrUnsupported is returned by every entry point on a platform that has no BSD termios: Linux and Windows builds compile, and fail cleanly.
Functions ¶
func BaseName ¶
BaseName strips the directory and the cu./tty. prefix from a device path, returning the name the two nodes share. A path that is not a serial node comes back unchanged.
func Drain ¶
Drain reads from r until the deadline passes or the buffer is full, returning everything that arrived. A timeout is not an error: it is the expected way for the call to end, and the byte count is the finding.
r must honour a read deadline, which is why it is asked for one: a reader that cannot time out would turn this into a hang.
func List ¶
List returns the base names of every serial port macOS currently publishes, sorted and deduplicated, so a port that has both nodes appears once. A platform with no /dev directory reports no ports rather than an error.
func Loopback ¶
Loopback returns a pseudo-terminal pair: an *os.File on the master side and the /dev/ttys* path of the slave, which Open accepts like any other tty.
It is the control instrument. Bytes written to the master come out of a Port opened on the slave path, so a probe can prove its reader works before concluding that a real device's silence means anything. The HID half of this module learned that lesson the expensive way.
Types ¶
type Config ¶
type Config struct {
// Baud is the line rate. Any positive value is accepted; macOS takes
// non-standard rates through IOSSIOSPEED after the termios call.
Baud int
// DataBits is 5, 6, 7 or 8. Zero means 8.
DataBits int
// StopBits is 1 or 2. Zero means 1.
StopBits int
// Parity defaults to none.
Parity Parity
// CLOCAL makes the driver ignore the carrier detect line. Without it a
// read on a /dev/tty.* node blocks until DCD is asserted, which on a
// USB CDC device that never raises DCD is forever.
CLOCAL bool
// HUPCL drops DTR on the last close.
HUPCL bool
// RTSCTS turns on hardware flow control. A device that does not drive CTS
// will never be written to once this is set.
RTSCTS bool
// ReadMin is VMIN: the smallest number of bytes a blocking read returns.
ReadMin uint8
// ReadTimeoutDecis is VTIME, in tenths of a second.
ReadTimeoutDecis uint8
}
Config is a line configuration. The zero value is not usable: Open rejects a zero baud rate rather than quietly picking one, because "the port was configured for 0 baud" is precisely the kind of silent misconfiguration that looks like a mute device.
func ConfigOf ¶
ConfigOf reads a termios back as a Config. It is the inverse of Config.Termios for the fields this package sets, and it exists so a probe can print what the kernel actually accepted rather than what it was asked for.
func (Config) String ¶
String renders a configuration the way a terminal program would: rate, then the classic 8N1 shorthand, then the flags that are on.
func (Config) Termios ¶
Termios renders the configuration as a raw-mode termios: no canonical line editing, no echo, no signal generation, no input or output translation. What arrives is what the device sent.
It applies the configuration to a copy of cur rather than building from zero, so the bits macOS set at open that this package has no opinion about survive.
type ErrBadConfig ¶
ErrBadConfig reports a configuration the kernel would reject or silently mangle.
func (*ErrBadConfig) Error ¶
func (e *ErrBadConfig) Error() string
Error renders the offending field.
type Lines ¶
type Lines uint32
Lines is a set of modem control line bits: LineDTR, LineDCD and friends.
const ( LineDTR Lines = 0x0002 LineRTS Lines = 0x0004 LineCTS Lines = 0x0020 LineDCD Lines = 0x0040 // TIOCM_CAR LineRI Lines = 0x0080 LineDSR Lines = 0x0100 )
Modem control line bits, as reported by TIOCMGET. They are typed Lines so that a mask built from them cannot be passed where a flag word is meant.
type Parity ¶
type Parity int
Parity selects the parity bit scheme.
The parity schemes a CDC-ACM device can be asked for.
type Port ¶
type Port struct {
// contains filtered or unexported fields
}
Port is an open serial port.
Reads and writes go through an *os.File on a non-blocking descriptor, so the Go runtime's poller services them and Port.SetReadDeadline works. That matters more than it sounds: a probe that cannot time out a read cannot tell "the device sent nothing" from "the program is wedged".
func Open ¶
Open opens the named device -- a full path such as /dev/cu.usbmodem14201 -- and applies cfg.
The descriptor is always opened O_NONBLOCK|O_NOCTTY, so opening a /dev/tty.* node does not hang waiting for carrier detect and the port does not become the process's controlling terminal. If cfg.CLOCAL is set the carrier requirement is then dropped for good; if it is not, a later blocking read on a dial-in node can still stall, which is the caller's choice to make.
func (*Port) Configure ¶
Configure applies cfg to an already open port.
The termios is read back after the write and compared, because tcsetattr is documented to succeed when it applied *some* of what it was asked for. A driver that silently refused the requested rate is the difference between a mute device and a misconfigured one, and this is the only place that distinction can be caught.
func (*Port) Flush ¶
Flush discards whatever the kernel has buffered in both directions, so a listen that follows starts from a known-empty queue.
func (*Port) Read ¶
Read reads from the port. It honours the deadline set by Port.SetReadDeadline, returning an error for which os.IsTimeout is true.
func (*Port) SetLines ¶
SetLines asserts the bits in set and clears the bits in clear. Passing LineDTR|LineRTS in set is the handshake a CDC device that waits for a host expects, and the one a /dev/cu.* node never performs on its own.
func (*Port) SetReadDeadline ¶
SetReadDeadline bounds subsequent reads.