Error anatomy
Every SerialPilotError instance has:
code— aSerialPilotErrorCodeenum value (e.g.'PORT_NOT_FOUND').message— a human-readable description.advice— what to actually do about it.path/baudRate— context, when known.cause— the underlying OS error, when one exists.
Catalogue
| Code | Class | Cause | Advice |
|---|---|---|---|
PORT_NOT_FOUND | PortNotFoundError | Device unplugged or path wrong | Check the cable; use findPorts() |
PERMISSION_DENIED | PermissionDeniedError | Insufficient OS permissions | Linux: add user to dialout |
PORT_BUSY | PortBusyError | Another app is holding the port | Close Arduino IDE, screen, PuTTY, etc. |
DISCONNECTED | DisconnectedError | Device unplugged mid-session | Use @serialpilot/reconnect |
OPEN_FAILED | OpenFailedError | Generic open failure | Inspect err.cause for the OS reason |
WRITE_FAILED | WriteFailedError | Wrote to a port that's gone | Check port.isOpen; honour backpressure |
READ_FAILED | ReadFailedError | Read from a port that's gone | Same as above |
CANCELLED | CancelledError | Operation aborted | Normal during close() — usually safe to ignore |
INVALID_ARGUMENT | InvalidArgumentError | Bad constructor args | Validate path and baudRate |
TIMEOUT | TimeoutError | Operation took too long | Bump the timeout, retry, or check the device |
Catching at construction time
import { SerialPilot, SerialPilotError, PortNotFoundError, PermissionDeniedError, } from 'serialpilot'
try { const port = new SerialPilot({ path: ‘/dev/ttyUSB0’, baudRate: 9600 }) } catch (err) { if (err instanceof PortNotFoundError) { console.error(err.advice) } else if (err instanceof PermissionDeniedError) { console.error(err.advice) } else if (err instanceof SerialPilotError) { console.error(${err.code}: ${err.message}) console.error(Advice: ${err.advice}) } }
Catching at runtime
Asynchronous failures arrive on the port's error event. Always attach a listener — an unhandled stream error crashes the Node process:
port.on('error', err => {
if (err instanceof DisconnectedError) {
// trigger reconnect logic, alert your fleet
} else if (err instanceof CancelledError) {
// pending operation cancelled by close() — usually fine
} else {
log.error({ code: err.code, advice: err.advice }, err.message)
}
})
Structured logging
Treat the error like a record:
logger.error({
code: err.code,
path: err.path,
baudRate: err.baudRate,
advice: err.advice,
cause: err.cause?.message,
}, err.message)
Now your dashboards can group by code rather than free-text matching, and alerts can target specific failure modes.
close(), but seeing it elsewhere usually means a destroy()/abort() happened mid-flight — which may or may not be a bug in your code.