From 32e3212cc0b8f9bc439576dccccfb0f69559a75e Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 6 Dec 2022 18:06:39 +0100 Subject: [PATCH] feat(axidraw): arbitrary unit support, measure draw time - add AxiDrawOpts.unitsPerInch to support any worldspace units - remove paperSize opt - add command constants for better mem use - update AxiDraw.draw() to measure & return time taken - add/update doc strings --- packages/axidraw/src/api.ts | 45 ++++++++++++++++++++----- packages/axidraw/src/axidraw.ts | 59 ++++++++++++++++++++++++--------- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/packages/axidraw/src/api.ts b/packages/axidraw/src/api.ts index 387ad221e4..93313fbac9 100644 --- a/packages/axidraw/src/api.ts +++ b/packages/axidraw/src/api.ts @@ -16,10 +16,16 @@ 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) */ +/** + * Pen up/down, optional delay (in ms), if omitted values used from + * {@link AxiDrawOpts}. + */ export type PenUpDownCommand = ["u" | "d", number?]; -/** Move to abs pos (in mm), optional speed factor (1 = normal, 0.5 = half speed) */ +/** + * Move to abs pos (in worldspace coords, default mm), optional speed factor + * (default: 1) + */ export type MoveXYCommand = ["m", ReadonlyVec, number?]; /** Explicit delay (in ms) */ @@ -37,11 +43,12 @@ export type DrawCommand = export interface AxiDrawOpts { /** - * Bounding rect of document in mm + * Conversion factor from geometry worldspace units to inches. + * Default units are millimeters. * - * @defaultValue [297, 210] + * @defaultValue 25.4 */ - pageSize: [number, number]; + unitsPerInch: number; /** * Hardware resolution (steps / inch) * @@ -51,7 +58,7 @@ export interface AxiDrawOpts { /** * Steps per second * - * @defaultValue 1500 + * @defaultValue 4000 */ speed: number; /** @@ -69,25 +76,31 @@ export interface AxiDrawOpts { /** * Delay after pen up * - * @defaultValue 0 + * @defaultValue 300 */ delayUp: number; /** * Delay after pen down * - * @defaultValue 0 + * @defaultValue 300 */ 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[]; /** @@ -95,3 +108,19 @@ export interface AxiDrawOpts { */ logger: ILogger; } + +export const START: StartCommand = ["start"]; + +export const STOP: StopCommand = ["stop"]; + +export const HOME: HomeCommand = ["home"]; + +export const PEN: PenConfigCommand = ["pen"]; + +export const UP: PenUpDownCommand = ["u"]; + +export const DOWN: PenUpDownCommand = ["d"]; + +export const ON: MotorCommand = ["on"]; + +export const OFF: MotorCommand = ["off"]; diff --git a/packages/axidraw/src/axidraw.ts b/packages/axidraw/src/axidraw.ts index 20531bd5d2..313a5148f8 100644 --- a/packages/axidraw/src/axidraw.ts +++ b/packages/axidraw/src/axidraw.ts @@ -1,14 +1,23 @@ import type { Fn0 } from "@thi.ng/api"; import { delayed } from "@thi.ng/compose"; -import { illegalState } from "@thi.ng/errors"; +import { assert, unsupported } from "@thi.ng/errors"; import { ConsoleLogger } from "@thi.ng/logger"; import { abs2, mulN2, ReadonlyVec, set2, sub2, Vec } from "@thi.ng/vectors"; import { SerialPort } from "serialport"; -import type { AxiDrawOpts, DrawCommand } from "./api.js"; +import { + AxiDrawOpts, + DOWN, + DrawCommand, + HOME, + OFF, + ON, + PEN, + UP, +} from "./api.js"; export const DEFAULT_OPTS: AxiDrawOpts = { logger: new ConsoleLogger("axidraw"), - pageSize: [297, 210], + unitsPerInch: 25.4, stepsPerInch: 2032, speed: 4000, up: 60, @@ -16,19 +25,26 @@ export const DEFAULT_OPTS: AxiDrawOpts = { delayUp: 300, delayDown: 300, preDelay: 0, - start: [["on"], ["pen"], ["u"]], - stop: [["u"], ["home"], ["off"]], + start: [ON, PEN, UP], + stop: [UP, HOME, OFF], }; export class AxiDraw { serial!: SerialPort; opts: AxiDrawOpts; + isConnected = false; pos: Vec = [0, 0]; constructor(opts: Partial = {}) { this.opts = { ...DEFAULT_OPTS, ...opts }; } + /** + * Async function. Attempts to connect to the drawing machine via given + * (partial) serial port path/name, returns true if successful. + * + * @param path + */ async connect(path: RegExp = /^\/dev\/tty\.usbmodem/) { for (let port of await SerialPort.list()) { if (path.test(port.path)) { @@ -37,6 +53,7 @@ export class AxiDraw { path: port.path, baudRate: 38400, }); + this.isConnected = true; return true; } } @@ -44,10 +61,12 @@ export class AxiDraw { } /** - * Converts sequence of {@link DrawCommand}s into actual EBB commands and - * sends them via configured serial port to the AxiDraw. The optional - * `cancel` predicate is checked prior to each individual command and - * processing is stopped if that function returns a truthy result. + * Async function. Converts sequence of {@link DrawCommand}s into actual EBB + * commands and sends them via configured serial port to the AxiDraw. The + * optional `cancel` predicate is checked prior to each individual command + * and processing is stopped if that function returns a truthy result. + * + * Returns number of milliseconds taken for drawing. * * @remarks * This function is async and if using `await` will only return once all @@ -70,11 +89,16 @@ export class AxiDraw { * @param cancel */ async draw(commands: Iterable, cancel?: Fn0) { + assert( + this.isConnected, + "AxiDraw not yet connected, need to call .connect() first" + ); + let t0 = Date.now(); if (!cancel) cancel = () => false; const { opts: config, pos } = this; - const { stepsPerInch, speed, preDelay } = config; - // scale factor: mm -> motor steps - const scale = stepsPerInch / 25.4; + const { stepsPerInch, unitsPerInch, speed, preDelay } = config; + // scale factor: worldspace units -> motor steps + const scale = stepsPerInch / unitsPerInch; let targetPos: Vec = [0, 0]; let delta: Vec = [0, 0]; for (let $cmd of commands) { @@ -98,6 +122,8 @@ export class AxiDraw { case "pen": { let val = a !== undefined ? a : config.down; + // unit ref: + // https://github.com/evil-mad/AxiDraw-Processing/blob/80d81a8c897b8a1872b0555af52a8d1b5b13cec4/AxiGen1/AxiGen1.pde#L213 this.send(`SC,5,${(7500 + 175 * val) | 0}\r`); val = b !== undefined ? b : config.up; this.send(`SC,4,${(7500 + 175 * val) | 0}\r`); @@ -129,14 +155,15 @@ export class AxiDraw { } break; default: - illegalState(`unknown command: ${$cmd}`); + unsupported(`unknown command: ${$cmd}`); } if (wait > 0) { - wait -= preDelay; + wait = Math.max(0, wait - preDelay); config.logger.debug(`waiting ${wait}ms...`); - await delayed(0, Math.max(0, wait)); + await delayed(0, wait); } } + return Date.now() - t0; } /** @@ -156,7 +183,7 @@ export class AxiDraw { const commands = pts.map((p) => ["m", p, speed]); return onlyGeo ? commands - : [["u"], commands[0], ["d"], ...commands.slice(1), ["u"]]; + : [UP, commands[0], DOWN, ...commands.slice(1), UP]; } protected send(msg: string) {