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

Support brushless servo upgrade #162

Closed
wants to merge 24 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f21920b
Ignore package-lock.json
alexrudd2 Jul 8, 2023
314bf32
Configure Renovate
renovate[bot] Jun 27, 2023
ee970e2
Run actions on origin-main
alexrudd2 Jun 28, 2023
d52ccbc
chore(deps): update dependency gh-pages to v5 (#34)
renovate[bot] Jun 27, 2023
9594f01
fix(deps): update dependency flatten-svg to ^0.3.0 (#4)
renovate[bot] Jun 27, 2023
f843320
fix(deps): update dependency svgdom to v0.1.14 (#27)
renovate[bot] Jun 27, 2023
f812b62
fix(deps): update dependency yargs to v17 (#47)
renovate[bot] Jun 30, 2023
fdb7be0
Revert "Ignore package-lock.json"
alexrudd2 Jul 8, 2023
05519e2
chore(deps): update typescript-eslint monorepo to v6
renovate[bot] Jul 10, 2023
7a52d3b
run actions on brushless
alexrudd2 Aug 18, 2023
bce31bb
Drop Node 14 (#81)
alexrudd2 Aug 30, 2023
86ebb2f
chore(deps): update dependency gh-pages to v6 (#78)
renovate[bot] Aug 30, 2023
6beb946
chore(deps): update dependency typescript to ~5.0 || ~5.2.0 (#70)
renovate[bot] Aug 30, 2023
c5b1f7b
chore(deps): update dependency @types/ws to 8.0.0 - 8.5 (#69)
renovate[bot] Aug 30, 2023
1d158ee
chore(deps): update actions/checkout action to v4
renovate[bot] Sep 4, 2023
a116669
Describe rpi 0/1 install process (#85)
jedahan Sep 8, 2023
3731448
Migrate from webpack to esbuild (#84)
jedahan Sep 8, 2023
18905d8
Call port.open() before querying it (#77)
alexrudd2 Sep 8, 2023
4123dff
fix(deps): update dependency svgdom to v0.1.16 (#82)
renovate[bot] Sep 8, 2023
0d08150
Break out Device into AxidrawBrushless
alexrudd2 Jul 8, 2023
fbb077e
Create a profile for AxiDrawBrushlessFast
alexrudd2 Jul 8, 2023
959d50d
Add brushless config to EBB
alexrudd2 Jul 8, 2023
990bcac
Use profile penUp instead of hardcoded values. Closes #178
alexrudd2 Aug 17, 2023
a2ee760
Add brushless to server
alexrudd2 Aug 17, 2023
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
Prev Previous commit
Next Next commit
Break out Device into AxidrawBrushless
alexrudd2 committed Sep 20, 2023
commit 0d0815046148aed6de49c6b083cc8e35b0e0416f
8 changes: 4 additions & 4 deletions src/__tests__/planning.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Plan, plan, Device, AxidrawFast, XYMotion, PenMotion} from '../planning';
import {Plan, plan, Axidraw, AxidrawFast, XYMotion, PenMotion} from '../planning';
import {Vec2} from '../vec';

describe("plan", () => {
@@ -25,7 +25,7 @@ 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: 0, y: 0}, penPos: Axidraw.penPctToPos(0)},
]);
});

@@ -35,7 +35,7 @@ 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: 20, y: 10}, to: {x: 0, y: 0}, penPos: Axidraw.penPctToPos(0)},
]);
});

@@ -50,7 +50,7 @@ describe("plan", () => {
{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: 20, y: 20}, to: {x: 0, y: 0}, penPos: Axidraw.penPctToPos(0)},
]);
});

18 changes: 9 additions & 9 deletions src/massager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Optimization from "optimize-paths";
import * as Planning from "./planning";
import {Device, Plan, PlanOptions} from "./planning";
import {Axidraw, Plan, PlanOptions} from "./planning";
import {dedupPoints, scaleToPaper, cropToMargins} from "./util";
import {Vec2, vmul, vrot} from "./vec";

@@ -71,21 +71,21 @@ 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, Axidraw.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),
penUpPos: Axidraw.penPctToPos(planOptions.penUpHeight),
penDownPos: Axidraw.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 * Axidraw.stepsPerMm,
maximumVelocity: planOptions.penDownMaxVelocity * Axidraw.stepsPerMm,
corneringFactor: planOptions.penDownCorneringFactor * Axidraw.stepsPerMm,
},
penUpProfile: {
acceleration: planOptions.penUpAcceleration * Device.Axidraw.stepsPerMm,
maximumVelocity: planOptions.penUpMaxVelocity * Device.Axidraw.stepsPerMm,
acceleration: planOptions.penUpAcceleration * Axidraw.stepsPerMm,
maximumVelocity: planOptions.penUpMaxVelocity * Axidraw.stepsPerMm,
corneringFactor: 0,
},
penDropDuration: planOptions.penDropDuration,
77 changes: 52 additions & 25 deletions src/planning.ts
Original file line number Diff line number Diff line change
@@ -87,36 +87,63 @@ interface ToolingProfile {
penDropDuration: number;
}

export const Device = {
Axidraw: {
stepsPerMm: 5,

// Practical min/max that you might ever want the pen servo to go on the AxiDraw (v2)
// Units: 83ns resolution pwm output.
// Defaults: penup at 12000 (1ms), pendown at 16000 (1.33ms).
penServoMin: 7500, // pen down
penServoMax: 28000, // pen up

penPctToPos(pct: number): number {
const t = pct / 100.0;
return Math.round(this.penServoMin * t + this.penServoMax * (1 - t));
}
interface Device {
stepsPerMm: number;

// Practical min/max that you might ever want the pen servo to go on the AxiDraw (v2)
// Units: 83ns resolution pwm output.
penServoMin: number; // pen down
penServoMax: number; // pen up
penPctToPos(pct: number): number;
}

export const Axidraw: Device = {
// default servo

stepsPerMm: 5,

// Practical min/max that you might ever want the pen servo to go on the AxiDraw (v2)
// Units: 83ns resolution pwm output.
// Defaults: penup at 12000 (1ms), pendown at 16000 (1.33ms).
penServoMin: 7500, // pen down
penServoMax: 28000, // pen up

penPctToPos(pct: number): number {
const t = pct / 100.0;
return Math.round(this.penServoMin * t + this.penServoMax * (1 - t));
}
};

export const AxidrawBrushless: Device = {
// brushless servo (https://shop.evilmadscientist.com/productsmenu/else?id=56)

stepsPerMm: 5,

// Practical min/max that you might ever want the pen servo to go on the AxiDraw (v2)
// Units: 83ns resolution pwm output.
// Defaults: penup at 12000 (1ms), pendown at 16000 (1.33ms).
penServoMin: 7500, // pen down
penServoMax: 28000, // pen up

penPctToPos(pct: number): number {
const t = pct / 100.0;
return Math.round(this.penServoMin * t + this.penServoMax * (1 - t));
}
};

export const AxidrawFast: ToolingProfile = {
penDownProfile: {
acceleration: 200 * Device.Axidraw.stepsPerMm,
maximumVelocity: 50 * Device.Axidraw.stepsPerMm,
corneringFactor: 0.127 * Device.Axidraw.stepsPerMm
acceleration: 200 * Axidraw.stepsPerMm,
maximumVelocity: 50 * Axidraw.stepsPerMm,
corneringFactor: 0.127 * Axidraw.stepsPerMm
},
penUpProfile: {
acceleration: 400 * Device.Axidraw.stepsPerMm,
maximumVelocity: 200 * Device.Axidraw.stepsPerMm,
acceleration: 400 * Axidraw.stepsPerMm,
maximumVelocity: 200 * Axidraw.stepsPerMm,
corneringFactor: 0
},
penUpPos: Device.Axidraw.penPctToPos(50),
penDownPos: Device.Axidraw.penPctToPos(60),
penUpPos: Axidraw.penPctToPos(50),
penDownPos: Axidraw.penPctToPos(60),
penDropDuration: 0.12,
penLiftDuration: 0.12,
};
@@ -292,9 +319,9 @@ export class Plan {
// pen-up/pen-down heights in a single place and reference them from
// the PenMotions. Then we can change them in just one place.
if (j === this.motions.length - 3) {
return new PenMotion(penDownHeight, Device.Axidraw.penPctToPos(0), motion.duration());
return new PenMotion(penDownHeight, Axidraw.penPctToPos(0), motion.duration());
} else if (j === this.motions.length - 1) {
return new PenMotion(Device.Axidraw.penPctToPos(0), penUpHeight, motion.duration());
return new PenMotion(Axidraw.penPctToPos(0), penUpHeight, motion.duration());
}
return (penMotionIndex++ % 2 === 0
? new PenMotion(penUpHeight, penDownHeight, motion.duration())
@@ -568,7 +595,7 @@ export function plan(
// then pick the pen up.
paths.forEach((p, i) => {
const m = constantAccelerationPlan(p, profile.penDownProfile);
const penUpPos = i === paths.length - 1 ? Device.Axidraw.penPctToPos(penMaxUpPos) : profile.penUpPos;
const penUpPos = i === paths.length - 1 ? Axidraw.penPctToPos(penMaxUpPos) : profile.penUpPos;
motions.push(
constantAccelerationPlan([curPos, m.p1], profile.penUpProfile),
new PenMotion(profile.penUpPos, profile.penDownPos, profile.penDropDuration),
@@ -579,6 +606,6 @@ export function plan(
});
// finally, move back to (0, 0).
motions.push(constantAccelerationPlan([curPos, {x: 0, y: 0}], profile.penUpProfile));
motions.push(new PenMotion(Device.Axidraw.penPctToPos(penMaxUpPos), profile.penUpPos, profile.penDropDuration));
motions.push(new PenMotion(Axidraw.penPctToPos(penMaxUpPos), profile.penUpPos, profile.penDropDuration));
return new Plan(motions);
}
4 changes: 2 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import { WakeLock } from "wake-lock";
import WebSocket from "ws";
import { SerialPortSerialPort } from "./serialport-serialport";
import { EBB } from "./ebb";
import { Device, PenMotion, Motion, Plan } from "./planning";
import { Axidraw, PenMotion, Motion, Plan } from "./planning";
import { formatDuration } from "./util";

export function startServer(port: number, device: string | null = null, enableCors = false, maxPayloadSize = "200mb") {
@@ -158,7 +158,7 @@ export function startServer(port: number, device: string | null = null, enableCo
await ebb.executeMotion(motion);
},
async postCancel(): Promise<void> {
await ebb.setPenHeight(Device.Axidraw.penPctToPos(0), 1000);
await ebb.setPenHeight(Axidraw.penPctToPos(0), 1000);
},
async postPlot(): Promise<void> {
await ebb.waitUntilMotorsIdle();
28 changes: 14 additions & 14 deletions src/ui.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import colormap from "colormap"

import {flattenSVG} from "flatten-svg";
import {PaperSize} from "./paper-size";
import {Device, Plan, PlanOptions, defaultPlanOptions, XYMotion, PenMotion} from "./planning";
import {Axidraw, Plan, PlanOptions, defaultPlanOptions, XYMotion, PenMotion} from "./planning";
import {formatDuration} from "./util";
import {Vec2} from "./vec";

@@ -168,7 +168,7 @@ class WebSerialDriver implements Driver {
}

if (this._cancelRequested) {
await this.ebb.setPenHeight(Device.Axidraw.penPctToPos(0), 1000);
await this.ebb.setPenHeight(Axidraw.penPctToPos(0), 1000);
if (this.oncancelled) this.oncancelled()
} else {
if (this.onfinished) this.onfinished()
@@ -240,7 +240,7 @@ class SaxiDriver implements Driver {

const websocketProtocol = document.location.protocol === "https:" ? "wss" : "ws";
this.socket = new WebSocket(`${websocketProtocol}://${document.location.host}/chat`);

this.socket.addEventListener("open", () => {
console.log(`Connected to EBB server.`);
this.connected = true;
@@ -356,8 +356,8 @@ const usePlan = (paths: Vec2[][] | null, planOptions: PlanOptions) => {
if (serialize(previousOptions) === serialize(newOptionsWithOldPenHeights)) {
// The existing plan should be the same except for penup/pendown heights.
return previousPlan.withPenHeights(
Device.Axidraw.penPctToPos(newOptions.penUpHeight),
Device.Axidraw.penPctToPos(newOptions.penDownHeight)
Axidraw.penPctToPos(newOptions.penUpHeight),
Axidraw.penPctToPos(newOptions.penDownHeight)
);
}
}
@@ -424,11 +424,11 @@ function PenHeight({state, driver}: {state: State; driver: Driver}) {
const setPenUpHeight = (x: number) => dispatch({type: "SET_PLAN_OPTION", value: {penUpHeight: x}});
const setPenDownHeight = (x: number) => dispatch({type: "SET_PLAN_OPTION", value: {penDownHeight: x}});
const penUp = () => {
const height = Device.Axidraw.penPctToPos(penUpHeight);
const height = Axidraw.penPctToPos(penUpHeight);
driver.setPenHeight(height, 1000);
};
const penDown = () => {
const height = Device.Axidraw.penPctToPos(penDownHeight);
const height = Axidraw.penPctToPos(penDownHeight);
driver.setPenHeight(height, 1000);
};
return <Fragment>
@@ -593,7 +593,7 @@ function PlanStatistics({plan}: {plan: Plan}) {

function TimeLeft({plan, progress, currentMotionStartedTime, paused}: {
plan: Plan;
progress: number | null;
progress: number | null;
currentMotionStartedTime: Date | null;
paused: boolean;
}) {
@@ -632,7 +632,7 @@ function PlanPreview(
}
) {
const ps = state.planOptions.paperSize;
const strokeWidth = state.visualizationOptions.penStrokeWidth * Device.Axidraw.stepsPerMm
const strokeWidth = state.visualizationOptions.penStrokeWidth * Axidraw.stepsPerMm
const colorPathsByStrokeOrder = state.visualizationOptions.colorPathsByStrokeOrder
const memoizedPlanPreview = useMemo(() => {
if (plan) {
@@ -644,7 +644,7 @@ function PlanPreview(
return m.blocks.map((b) => b.p1).concat([m.p2]);
} else { return []; }
}).filter((m) => m.length);
return <g transform={`scale(${1 / Device.Axidraw.stepsPerMm})`}>
return <g transform={`scale(${1 / Axidraw.stepsPerMm})`}>
{lines.map((line, i) =>
<path
key={i}
@@ -692,7 +692,7 @@ function PlanPreview(
const pos = motion instanceof XYMotion
? motion.instant(Math.min(microprogress / 1000, motion.duration())).p
: (plan.motion(state.progress - 1) as XYMotion).p2;
const {stepsPerMm} = Device.Axidraw;
const {stepsPerMm} = Axidraw;
const posXMm = pos.x / stepsPerMm;
const posYMm = pos.y / stepsPerMm;
progressIndicator =
@@ -1159,9 +1159,9 @@ function Root() {
<div className="section-header">plot</div>
<div className="section-body section-body__plot">
<PlanStatistics plan={plan} />
<TimeLeft
plan={plan}
progress={state.progress}
<TimeLeft
plan={plan}
progress={state.progress}
currentMotionStartedTime={currentMotionStartedTime}
paused={state.paused}
/>