Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for brushless hardware #94

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/__tests__/planning.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {Plan, plan, Device, AxidrawFast, XYMotion, PenMotion} from '../planning';
import {Plan, plan, Device, AxidrawFast, XYMotion, PenMotion, defaultPlanOptions} from '../planning';
import {Vec2} from '../vec';

describe("plan", () => {
const device = Device()
const positions = {
up: AxidrawFast.penUpPos,
down: AxidrawFast.penDownPos,
zero: device.penPctToPos(0)
}
it.skip("handles an empty input", () => {
expect(plan([], AxidrawFast)).toEqual(new Plan([]))
expect(plan([], AxidrawFast)).toEqual(new Plan([], positions.zero))
});

function xyMotions(plan: Plan) {
Expand All @@ -24,8 +30,8 @@ describe("plan", () => {

expect(xyMotions(p)).toEqual([
{from: {x: 0, y: 0}, to: {x: 10, y: 10}, penPos: 0},
{from: {x: 10, y: 10}, to: {x: 10, y: 10}, penPos: AxidrawFast.penDownPos},
{from: {x: 10, y: 10}, to: {x: 0, y: 0}, penPos: Device.Axidraw.penPctToPos(0)},
{from: {x: 10, y: 10}, to: {x: 10, y: 10}, penPos: positions.up},
alexrudd2 marked this conversation as resolved.
Show resolved Hide resolved
{from: {x: 10, y: 10}, to: {x: 0, y: 0}, penPos: positions.zero},
]);
});

Expand All @@ -34,8 +40,8 @@ describe("plan", () => {

expect(xyMotions(p)).toEqual([
{from: {x: 0, y: 0}, to: {x: 10, y: 10}, penPos: 0},
{from: {x: 10, y: 10}, to: {x: 20, y: 10}, penPos: AxidrawFast.penDownPos},
{from: {x: 20, y: 10}, to: {x: 0, y: 0}, penPos: Device.Axidraw.penPctToPos(0)},
{from: {x: 10, y: 10}, to: {x: 20, y: 10}, penPos: positions.up},
{from: {x: 20, y: 10}, to: {x: 0, y: 0}, penPos: positions.zero},
]);
});

Expand All @@ -47,10 +53,10 @@ describe("plan", () => {

expect(xyMotions(p)).toEqual([
{from: {x: 0, y: 0}, to: {x: 10, y: 10}, penPos: 0},
{from: {x: 10, y: 10}, to: {x: 20, y: 10}, penPos: AxidrawFast.penDownPos},
{from: {x: 20, y: 10}, to: {x: 10, y: 20}, penPos: AxidrawFast.penUpPos},
{from: {x: 10, y: 20}, to: {x: 20, y: 20}, penPos: AxidrawFast.penDownPos},
{from: {x: 20, y: 20}, to: {x: 0, y: 0}, penPos: Device.Axidraw.penPctToPos(0)},
{from: {x: 10, y: 10}, to: {x: 20, y: 10}, penPos: positions.down},
{from: {x: 20, y: 10}, to: {x: 10, y: 20}, penPos: positions.up},
{from: {x: 10, y: 20}, to: {x: 20, y: 20}, penPos: positions.down},
{from: {x: 20, y: 20}, to: {x: 0, y: 0}, penPos: positions.zero},
]);
});

Expand Down
92 changes: 43 additions & 49 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,28 @@ import { Vec2 } from "./vec";
import { formatDuration } from "./util";
import { Device, PlanOptions, defaultPlanOptions } from "./planning";
import { PaperSize } from "./paper-size";
import { Hardware } from "./ebb";

function parseSvg(svg: string) {
const window = new Window
function parseSvg (svg: string) {
const window = new Window()
window.document.documentElement.innerHTML = svg
return window.document.documentElement
}

export function cli(argv: string[]): void {
yargs.strict()
.option("device", {
alias: "d",
describe: "device to connect to",
type: "string"
export function cli (argv: string[]): void {
yargs
.strict()
.option('hardware', {
describe: 'select hardware type',
choices: ['v3', 'brushless'] as const,
default: 'v3',
coerce: value => value as Hardware
})
.option('device', {
alias: 'd',
describe: 'device to connect to',
type: 'string'
})
.command('$0', 'run the saxi web server',
yargs => yargs
.option("port", {
alias: "p",
default: Number(process.env.PORT || 9080),
describe: "TCP port on which to listen",
type: "number"
})
.option("enable-cors", {
describe: "enable cross-origin resource sharing (CORS)",
type: "boolean"
})
.option("max-payload-size", {
describe: "maximum payload size to accept",
default: "200 mb",
type: "string"
})
.option("firmware-version", {
describe: "print the device's firmware version and exit",
type: "boolean"
}),
args => {
if (args["firmware-version"]) {
connectEBB(args.device).then(async (ebb) => {
if (!ebb) {
console.error(`No EBB connected`);
return process.exit(1);
}
const fwv = await ebb.firmwareVersion();
console.log(fwv);
await ebb.close();
});
} else {
startServer(args.port, args.device, args["enable-cors"], args["max-payload-size"]);
}
}
)
.command('plot <file>', 'plot an svg, then exit',
yargs => yargs
.positional("file", {
Expand Down Expand Up @@ -205,6 +176,7 @@ export function cli(argv: string[]): void {
const planOptions: PlanOptions = {
paperSize,
marginMm: args.margin,
hardware: args.hardware,

selectedGroupLayers: new Set([]), // TODO
selectedStrokeLayers: new Set([]), // TODO
Expand Down Expand Up @@ -234,7 +206,7 @@ export function cli(argv: string[]): void {
const p = replan(linesToVecs(lines), planOptions)
console.log(`${p.motions.length} motions, estimated duration: ${formatDuration(p.duration())}`)
console.log("connecting to plotter...")
const ebb = await connectEBB(args.device)
const ebb = await connectEBB(args.hardware, args.device)
if (!ebb) {
console.error("Couldn't connect to device!")
process.exit(1)
Expand All @@ -251,17 +223,39 @@ export function cli(argv: string[]): void {
.check(args => args.percent >= 0 && args.percent <= 100),
async args => {
console.log('connecting to plotter...')
const ebb = await connectEBB(args.device)
const ebb = await connectEBB(args.hardware, args.device)
if (!ebb) {
console.error("Couldn't connect to device!")
process.exit(1)
}
await ebb.setPenHeight(Device.Axidraw.penPctToPos(args.percent), 1000)
const device = Device(ebb.hardware)
await ebb.setPenHeight(device.penPctToPos(args.percent), 1000)

console.log(`moving to ${args.percent}%...`)
await ebb.close()
})
.parse(argv);
.command('$0', 'run the saxi web server',
args => args
.option('port', {
alias: 'p',
describe: 'TCP port on which to listen',
default: 9080,
type: 'number',
})
.option('enable-cors', {
describe: 'enable cross-origin resource sharing (CORS)',
default: false,
type: 'boolean',
})
.option('max-payload-size', {
describe: 'maximum payload size to accept',
default: '200mb',
}),
args => {
startServer(args.port, args.hardware, args.device, args['enable-cors'], args['max-payload-size'])
}
)
.parse(argv)
}

function linesToVecs(lines: any[]): Vec2[][] {
Expand Down
18 changes: 12 additions & 6 deletions src/ebb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ function modf(d: number): [number, number] {
return [fracPart, intPart];
}

export type Hardware = 'v3' | 'brushless'

export class EBB {
public port: SerialPort;
private commandQueue: Iterator<any, any, Buffer>[];
private writer: WritableStreamDefaultWriter<Uint8Array>;
private readableClosed: Promise<void>
public hardware: Hardware

private microsteppingMode = 0;

Expand All @@ -22,10 +25,11 @@ export class EBB {

private cachedFirmwareVersion: [number, number, number] | undefined = undefined;

public constructor(port: SerialPort) {
this.port = port;
this.writer = this.port.writable.getWriter();
this.commandQueue = [];
public constructor (port: SerialPort, hardware: Hardware = 'v3') {
this.hardware = hardware
this.port = port
this.writer = this.port.writable.getWriter()
this.commandQueue = []
this.readableClosed = port.readable
.pipeThrough(new RegexParser({ regex: /[\r\n]+/ }))
.pipeTo(new WritableStream({
Expand Down Expand Up @@ -155,8 +159,10 @@ export class EBB {
await this.command(`SR,${(timeout * 1000) | 0}${power != null ? `,${power ? 1 : 0}` : ''}`)
}

public setPenHeight(height: number, rate: number, delay = 0): Promise<void> {
return this.command(`S2,${height},4,${rate},${delay}`);
// https://evil-mad.github.io/EggBot/ebb.html#S2 General RC Servo Output
public async setPenHeight (height: number, rate: number, delay = 0): Promise<void> {
const output_pin = this.hardware === 'v3' ? 4 : 5
return await this.command(`S2,${height},${output_pin},${rate},${delay}`)
}

public lowlevelMove(
Expand Down
32 changes: 16 additions & 16 deletions src/massager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as Optimization from "optimize-paths";
import * as Planning from "./planning";
import {Device, Plan, PlanOptions} from "./planning";
import {Device, Plan, PlanOptions, plan} from "./planning";
import {dedupPoints, scaleToPaper, cropToMargins} from "./util";
import {Vec2, vmul, vrot} from "./vec";

Expand All @@ -10,8 +9,9 @@ const svgUnitsPerInch = 96
const mmPerInch = 25.4
const mmPerSvgUnit = mmPerInch / svgUnitsPerInch

export function replan(inPaths: Vec2[][], planOptions: PlanOptions): Plan {
let paths = inPaths;
export function replan (inPaths: Vec2[][], planOptions: PlanOptions): Plan {
let paths = inPaths
const device = Device(planOptions.hardware)

// Rotate drawing around center of paper to handle plotting portrait drawings
// along y-axis of plotter
Expand Down Expand Up @@ -71,27 +71,27 @@ export function replan(inPaths: Vec2[][], planOptions: PlanOptions): Plan {
}

// Convert the paths to units of "steps".
paths = paths.map((ps) => ps.map((p) => vmul(p, Device.Axidraw.stepsPerMm)));
paths = paths.map((ps) => ps.map((p) => vmul(p, device.stepsPerMm)))

// And finally, motion planning.
console.time("planning pen motions");
const plan = Planning.plan(paths, {
penUpPos: Device.Axidraw.penPctToPos(planOptions.penUpHeight),
penDownPos: Device.Axidraw.penPctToPos(planOptions.penDownHeight),
console.time('planning pen motions')
const theplan = plan(paths, {
penUpPos: device.penPctToPos(planOptions.penUpHeight),
penDownPos: device.penPctToPos(planOptions.penDownHeight),
penDownProfile: {
acceleration: planOptions.penDownAcceleration * Device.Axidraw.stepsPerMm,
maximumVelocity: planOptions.penDownMaxVelocity * Device.Axidraw.stepsPerMm,
corneringFactor: planOptions.penDownCorneringFactor * Device.Axidraw.stepsPerMm,
acceleration: planOptions.penDownAcceleration * device.stepsPerMm,
maximumVelocity: planOptions.penDownMaxVelocity * device.stepsPerMm,
corneringFactor: planOptions.penDownCorneringFactor * device.stepsPerMm
},
penUpProfile: {
acceleration: planOptions.penUpAcceleration * Device.Axidraw.stepsPerMm,
maximumVelocity: planOptions.penUpMaxVelocity * Device.Axidraw.stepsPerMm,
corneringFactor: 0,
acceleration: planOptions.penUpAcceleration * device.stepsPerMm,
maximumVelocity: planOptions.penUpMaxVelocity * device.stepsPerMm,
corneringFactor: 0
},
penDropDuration: planOptions.penDropDuration,
penLiftDuration: planOptions.penLiftDuration,
});
console.timeEnd("planning pen motions");

return plan;
return theplan
}
Loading