thi-ng/umbrella

View on GitHub
packages/axidraw/src/api.ts

Summary

Maintainability
A
1 hr
Test Coverage
import type { IDeref } from "@thi.ng/api";
import type { ILogger } from "@thi.ng/logger";
import type { Quantity } from "@thi.ng/units";
import type { ReadonlyVec } from "@thi.ng/vectors";

/** Start command sequence (configurable via {@link AxiDrawOpts}) */
export type StartCommand = ["start"];

/** Stop command sequence (configurable via {@link AxiDrawOpts}) */
export type StopCommand = ["stop"];

/** Return plotter to initial XY position */
export type HomeCommand = ["home"];

/** Reset curr position as home (0,0) */
export type ResetCommand = ["reset"];

/** Turn XY motors on/off */
export type MotorCommand = ["on" | "off"];

/** Pen config, min/down position, max/up position (in %) */
export type PenConfigCommand = ["pen", number?, number?];

/**
 * Pen up/down, optional delay (in ms), optional custom level/position. If
 * omitted, default values used from {@link AxiDrawOpts}. Using -1 as delay also
 * uses default.
 */
export type PenUpDownCommand = ["u" | "d", number?, number?];

/**
 * Move to abs pos (in worldspace coords, default mm), optional speed factor
 * (default: 1)
 */
export type MoveXYCommand = ["M", ReadonlyVec, number?];

/**
 * Move to **relative** pos (based on curr plotter position, im worldspace
 * units, default mm), optional speed factor (default: 1)
 */
export type MoveRelCommand = ["m", ReadonlyVec, number?];

/** Explicit delay (in ms) */
export type WaitCommand = ["w", number];

/** Ignored, but will be logged (if logging enabled) */
export type CommentCommand = ["comment", string];

/**
 * Stores current pen configuration on stack.
 */
export type SaveCommand = ["save"];

/**
 * Restores current pen configuration from stack.
 */
export type RestoreCommand = ["restore"];

export type DrawCommand =
    | CommentCommand
    | HomeCommand
    | MotorCommand
    | MoveRelCommand
    | MoveXYCommand
    | PenConfigCommand
    | PenUpDownCommand
    | ResetCommand
    | RestoreCommand
    | SaveCommand
    | StartCommand
    | StopCommand
    | WaitCommand;

/**
 * Global plotter drawing configuration. Also see {@link DEFAULT_OPTS}.
 */
export interface AxiDrawOpts {
    /**
     * Serial connection to use (only used for testing/dev purposes, otherwise
     * leave default).
     *
     * @defaultValue {@link SERIAL_PORT}
     */
    serial: SerialConnection;
    /**
     * Logger instance for outputting draw commands, state info and metrics.
     */
    logger: ILogger;
    /**
     * Optional implementation to pause, resume or cancel the processing of
     * drawing commands (see {@link AxiDrawControl} for default impl).
     *
     * @remarks
     * If a control is provided, it will be checked prior to processing each
     * individual command. Drawing will be paused if the control state is in
     * {@link AxiDrawState.PAUSE} state and the control will be rechecked every
     * {@link AxiDrawOpts.refresh} milliseconds for updates. In paused state,
     * the pen will be automatically lifted (if it wasn't already) and when
     * resuming it will be sent down again (if it was originally down).
     *
     * Draw commands are only sent to the machine if no control is provided at
     * all or if the control is in the {@link AxiDrawState.CONTINUE} state.
     */
    control?: IDeref<AxiDrawState>;
    /**
     * All XY coords will be clamped to given bounding rect, either defined by
     * `[[minX,minY], [maxX,maxY]]` (in worldspace units) or as paper size
     * defined as a
     * [`quantity`](https://docs.thi.ng/umbrella/units/functions/quantity-1.html).
     * The default value is DIN A3 landscape.
     *
     * @remarks
     * Set to `undefined` to disable bounds/clipping. If given a paper size (via
     * thi.ng/units `quantity()`), the units used to define these dimensions are
     * irrelevant (and independent of {@link AxiDrawOpts.unitsPerInch}!) and
     * will be automatically converted. Also, the resulting bounds will always
     * be based on [0, 0].
     *
     * List of paper sizes/presets:
     * https://github.com/thi-ng/umbrella/blob/develop/packages/units/README.md#constants
     *
     * Also see {@link AxiDrawOpts.unitsPerInch}
     *
     * @defaultValue `DIN_A3_LANDSCAPE`
     */
    bounds?: [ReadonlyVec, ReadonlyVec] | Quantity<number[]>;
    /**
     * Conversion factor from geometry worldspace units to inches.
     * Default units are millimeters.
     *
     * @defaultValue 25.4
     */
    unitsPerInch: number;
    /**
     * Hardware resolution (steps / inch)
     *
     * @defaultValue 2032
     */
    stepsPerInch: number;
    /**
     * Steps per second for XY movements whilst pen down
     *
     * @defaultValue 4000
     */
    speedDown: number;
    /**
     * Steps per second for XY movements whilst pen up
     *
     * @defaultValue 4000
     */
    speedUp: number;
    /**
     * Up position (%)
     *
     * @defaultValue 60
     */
    up: number;
    /**
     * Up position (%)
     *
     * @defaultValue 30
     */
    down: number;
    /**
     * Delay after pen up
     *
     * @defaultValue 150
     */
    delayUp: number;
    /**
     * Delay after pen down
     *
     * @defaultValue 150
     */
    delayDown: number;
    /**
     * Time in ms to subtract from actual delay time until next command
     *
     * @defaultValue 0
     */
    preDelay: number;
    /**
     * Sequence for `start` {@link DrawCommand}
     *
     * @defaultValue `[ON, PEN, UP]`
     */
    start: DrawCommand[];
    /**
     * Sequence for `end` {@link DrawCommand}
     *
     * @defaultValue `[UP, HOME, OFF]`
     */
    stop: DrawCommand[];
    /**
     * Position (in world space units) which is considered the origin and which
     * will be added to all coordinates as offset (e.g. for plotting with a
     * physically offset easel).
     *
     * @remarks
     * Note: The configured {@link AxiDrawOpts.bounds} are independent from this
     * setting and are intended to enforce the plotter's physical movement
     * limits. E.g. shifting the home/offset position by +100mm along X (and
     * assuming only positive coordinates will be used for the drawing), simply
     * means the available horizontal drawing space will be reduced by the same
     * amount...
     *
     * @defaultValue [0, 0]
     */
    home: ReadonlyVec;
    /**
     * Refresh interval for checking the control FSM in paused state.
     *
     * @defaultValue 1000
     */
    refresh: number;
    /**
     * If true (default), installs SIGINT handler to lift pen when the Node.js
     * process is terminated.
     */
    sigint: boolean;
}

/**
 * FSM state enum for (interactive) control for processing of drawing commands.
 * See {@link AxiDraw.draw} and {@link AxiDrawControl} for details.
 */
export enum AxiDrawState {
    /**
     * Draw command processing can continue as normal.
     */
    CONTINUE,
    /**
     * Draw command processing is suspended indefinitely.
     */
    PAUSE,
    /**
     * Draw command processing is cancelled.
     */
    CANCEL,
}

/**
 * Drawing behavior options for a single polyline.
 */
export interface PolylineOpts {
    /**
     * Speed factor (multiple of globally configured draw speed). Depending on
     * pen used, slower speeds might result in thicker strokes.
     *
     * @defaultValue 1
     */
    speed: number;
    /**
     * Pen down (Z) position (%) for this particular shape/polyline. Will be
     * reset to globally configured default at the end of the shape.
     */
    down: number;
    /**
     * Delay for pen down command at the start of this particular
     * shape/polyline.
     */
    delayDown: number;
    /**
     * Delay for pen up command at the end this particular shape/polyline.
     */
    delayUp: number;
    /**
     * If enabled, no pen up/down commands will be included.
     * {@link PolylineOpts.speed} is the only other option considered then.
     *
     * @defaultValue false
     */
    onlyGeo: boolean;
}

/**
 * Metrics returned by {@link AxiDraw.draw}.
 */
export interface Metrics {
    /**
     * Total number of milliseconds taken for drawing all given commands (incl.
     * any pauses caused by the control)
     */
    duration: number;
    /**
     * Total draw distance, i.e. distance traveled whilst pen down (in original
     * user units, see {@link AxiDrawOpts.unitsPerInch}).
     */
    drawDist: number;
    /**
     * Total traveled, incl. any movements without drawing (in original user
     * units, see {@link AxiDrawOpts.unitsPerInch}).
     */
    totalDist: number;
    /**
     * Number of pen up/down commands (useful for measuring servo lifespan).
     */
    penCommands: number;
    /**
     * Total number of {@link DrawCommand}s processed.
     */
    commands: number;
}

export interface SerialConnection {
    /**
     * Async function. Returns a list of available serial ports. The arg given
     * is the path requested by the user when calling {@link AxiDraw.connect}.
     *
     * @param path
     */
    list(path: string): Promise<{ path: string }[]>;
    /**
     * Returns an actual serial port (or mock) instance, is given the first
     * matching path in array returned by {@link SerialConnection.list}.
     *
     * @param path
     * @param baudRate
     */
    ctor(path: string, baudRate: number): ISerial;
}

export interface ISerial {
    close(): void;
    /**
     * Writes given string to the port.
     *
     * @param msg
     */
    write(msg: string): void;
}