saxi
Version:
Drive the AxiDraw pen plotter
390 lines • 17.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
exports.waitForEbb = waitForEbb;
exports.connectEBB = connectEBB;
const cors_1 = __importDefault(require("cors"));
require("web-streams-polyfill/polyfill");
const express_1 = __importDefault(require("express"));
const node_http_1 = __importDefault(require("node:http"));
const node_path_1 = __importDefault(require("node:path"));
const wake_lock_1 = require("wake-lock");
const ws_1 = __importDefault(require("ws"));
const serialport_serialport_1 = require("./serialport-serialport");
const planning_1 = require("./planning");
const util_1 = require("./util");
const bindings_cpp_1 = require("@serialport/bindings-cpp");
const _self = __importStar(require("./server")); // use self-import for test mocking
const ebb_1 = require("./ebb");
const getDeviceInfo = (ebb, com) => {
return { com: ebb ? com : null, hardware: ebb === null || ebb === void 0 ? void 0 : ebb.hardware };
};
function startServer(port_1) {
return __awaiter(this, arguments, void 0, function* (port, hardware = 'v3', com = null, enableCors = false, maxPayloadSize = '200mb') {
const app = (0, express_1.default)();
app.use('/', express_1.default.static(node_path_1.default.join(__dirname, '..', 'ui')));
app.use(express_1.default.json({ limit: maxPayloadSize }));
if (enableCors) {
app.use((0, cors_1.default)());
}
const server = node_http_1.default.createServer(app);
const wss = new ws_1.default.Server({ server });
let ebb;
let clients = [];
let cancelRequested = false;
let unpaused = null;
let signalUnpause = null;
let motionIdx = null;
let currentPlan = null;
let plotting = false;
wss.on("connection", (ws) => {
clients.push(ws);
ws.on("message", (message) => {
const msg = JSON.parse(message.toString());
switch (msg.c) {
case "ping":
ws.send(JSON.stringify({ c: "pong" }));
break;
case "limp":
if (ebb) {
ebb.disableMotors();
}
break;
case "setPenHeight":
if (ebb) {
(() => __awaiter(this, void 0, void 0, function* () {
if (yield ebb.supportsSR()) {
yield ebb.setServoPowerTimeout(10000, true);
}
yield ebb.setPenHeight(msg.p.height, msg.p.rate);
}))();
}
break;
}
});
ws.send(JSON.stringify({ c: 'dev', p: getDeviceInfo(ebb, com) }));
ws.send(JSON.stringify({ c: "pause", p: { paused: !!unpaused } }));
if (motionIdx != null) {
ws.send(JSON.stringify({ c: "progress", p: { motionIdx } }));
}
if (currentPlan != null) {
ws.send(JSON.stringify({ c: "plan", p: { plan: currentPlan } }));
}
ws.on("close", () => {
clients = clients.filter((w) => w !== ws);
});
});
app.post("/plot", (req, res) => __awaiter(this, void 0, void 0, function* () {
if (plotting) {
console.log("Received plot request, but a plot is already in progress!");
return res.status(400).end('Plot in progress');
}
plotting = true;
try {
const plan = planning_1.Plan.deserialize(req.body);
currentPlan = req.body;
console.log(`Received plan of estimated duration ${(0, util_1.formatDuration)(plan.duration())}`);
console.log(ebb != null ? "Beginning plot..." : "Simulating plot...");
res.status(200).end();
const begin = Date.now();
let wakeLock;
// The wake-lock module is macOS-only.
if (process.platform === 'darwin') {
try {
wakeLock = new wake_lock_1.WakeLock("saxi plotting");
}
catch (e) {
console.warn("Couldn't acquire wake lock. Ensure your machine does not sleep during plotting");
}
}
try {
yield doPlot(ebb != null ? realPlotter : simPlotter, plan);
const end = Date.now();
console.log(`Plot took ${(0, util_1.formatDuration)((end - begin) / 1000)}`);
}
finally {
if (wakeLock) {
wakeLock.release();
}
}
}
finally {
plotting = false;
}
}));
app.post("/cancel", (req, res) => {
cancelRequested = true;
if (unpaused) {
signalUnpause();
}
unpaused = signalUnpause = null;
res.status(200).end();
});
app.post("/pause", (req, res) => {
if (!unpaused) {
unpaused = new Promise(resolve => {
signalUnpause = resolve;
});
broadcast({ c: "pause", p: { paused: true } });
}
res.status(200).end();
});
app.post("/resume", (req, res) => {
if (signalUnpause) {
signalUnpause();
signalUnpause = unpaused = null;
}
res.status(200).end();
});
function broadcast(msg) {
clients.forEach((ws) => {
try {
ws.send(JSON.stringify(msg));
}
catch (e) {
console.warn(e);
}
});
}
const realPlotter = {
prePlot(initialPenHeight) {
return __awaiter(this, void 0, void 0, function* () {
yield ebb.enableMotors(2);
yield ebb.setPenHeight(initialPenHeight, 1000, 1000);
});
},
executeMotion(motion, _progress) {
return __awaiter(this, void 0, void 0, function* () {
yield ebb.executeMotion(motion);
});
},
postCancel() {
return __awaiter(this, void 0, void 0, function* () {
const device = (0, planning_1.Device)(ebb.hardware);
// TODO: switch to pen up position
yield ebb.setPenHeight(device.penPctToPos(50), 1000);
});
},
postPlot() {
return __awaiter(this, void 0, void 0, function* () {
yield ebb.waitUntilMotorsIdle();
yield ebb.disableMotors();
});
},
};
const simPlotter = {
// eslint-disable-next-line @typescript-eslint/no-empty-function
prePlot(_initialPenHeight) {
return __awaiter(this, void 0, void 0, function* () {
});
},
executeMotion(motion, progress) {
return __awaiter(this, void 0, void 0, function* () {
console.log(`Motion ${progress[0] + 1}/${progress[1]}`);
yield new Promise((resolve) => setTimeout(resolve, motion.duration() * 1000));
});
},
postCancel() {
return __awaiter(this, void 0, void 0, function* () {
console.log("Plot cancelled");
});
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
postPlot() {
return __awaiter(this, void 0, void 0, function* () {
});
},
};
function doPlot(plotter, plan) {
return __awaiter(this, void 0, void 0, function* () {
cancelRequested = false;
unpaused = null;
signalUnpause = null;
motionIdx = 0;
const firstPenMotion = plan.motions.find((x) => x instanceof planning_1.PenMotion);
yield plotter.prePlot(firstPenMotion.initialPos);
let penIsUp = true;
for (const motion of plan.motions) {
broadcast({ c: "progress", p: { motionIdx } });
yield plotter.executeMotion(motion, [motionIdx, plan.motions.length]);
if (motion instanceof planning_1.PenMotion) {
penIsUp = motion.initialPos < motion.finalPos;
}
if (unpaused && penIsUp) {
yield unpaused;
broadcast({ c: "pause", p: { paused: false } });
}
if (cancelRequested) {
break;
}
motionIdx += 1;
}
motionIdx = null;
currentPlan = null;
if (cancelRequested) {
yield plotter.postCancel();
broadcast({ c: "cancelled" });
cancelRequested = false;
}
else {
broadcast({ c: "finished" });
}
yield plotter.postPlot();
});
}
return new Promise((resolve) => {
server.listen(port, () => {
function connect() {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
const devices = ebbs(com, hardware);
try {
for (var _d = true, devices_1 = __asyncValues(devices), devices_1_1; devices_1_1 = yield devices_1.next(), _a = devices_1_1.done, !_a; _d = true) {
_c = devices_1_1.value;
_d = false;
const device = _c;
ebb = device;
broadcast({ c: 'dev', p: getDeviceInfo(ebb, com) });
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = devices_1.return)) yield _b.call(devices_1);
}
finally { if (e_1) throw e_1.error; }
}
});
}
connect();
const { family, address, port } = server.address();
const addr = `${family === "IPv6" ? `[${address}]` : address}:${port}`;
console.log(`Server listening on http://${addr}`);
resolve(server);
});
});
});
}
function tryOpen(com) {
return __awaiter(this, void 0, void 0, function* () {
const port = new serialport_serialport_1.SerialPortSerialPort(com);
yield port.open({ baudRate: 9600 });
return port;
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function isEBB(p) {
return p.manufacturer === "SchmalzHaus" || p.manufacturer === "SchmalzHaus LLC" || (p.vendorId == "04D8" && p.productId == "FD92");
}
function listEBBs() {
return __awaiter(this, void 0, void 0, function* () {
const Binding = (0, bindings_cpp_1.autoDetect)();
const ports = yield Binding.list();
return ports.filter(isEBB).map((p) => p.path);
});
}
function waitForEbb() {
return __awaiter(this, void 0, void 0, function* () {
// eslint-disable-next-line no-constant-condition
while (true) {
const ebbs = yield listEBBs();
if (ebbs.length) {
return ebbs[0];
}
yield sleep(5000);
}
});
}
function ebbs(path_1) {
return __asyncGenerator(this, arguments, function* ebbs_1(path, hardware = 'v3') {
while (true) {
try {
const com = path || (yield __await(_self.waitForEbb())); // use self-import for test mocking
console.log(`Found EBB at ${com}`);
const port = yield __await(tryOpen(com));
const closed = new Promise((resolve) => {
port.addEventListener('disconnect', resolve, { once: true });
});
yield yield __await(new ebb_1.EBB(port, hardware));
yield __await(closed);
yield yield __await(null);
console.error("Lost connection to EBB, reconnecting...");
}
catch (e) {
console.error(`Error connecting to EBB: ${e.message}`);
console.error("Retrying in 5 seconds...");
yield __await(sleep(5000));
}
}
});
}
function connectEBB() {
return __awaiter(this, arguments, void 0, function* (hardware = 'v3', device) {
if (!device) {
const ebbs = yield listEBBs();
if (ebbs.length === 0)
return null;
device = ebbs[0];
}
const port = yield tryOpen(device);
return new ebb_1.EBB(port, hardware);
});
}
//# sourceMappingURL=server.js.map