add bambu module
This commit is contained in:
parent
6f9e066d13
commit
98190a39e5
7 changed files with 476 additions and 16 deletions
42
app.js
42
app.js
|
|
@ -302,6 +302,7 @@ function init(mod) {
|
||||||
mod.add(fixedmap("/api/", api));
|
mod.add(fixedmap("/api/", api));
|
||||||
if (debug) {
|
if (debug) {
|
||||||
mod.static("/mod/", "mod");
|
mod.static("/mod/", "mod");
|
||||||
|
mod.static("/mods/", "mods");
|
||||||
mod.sync("/reload", () => {
|
mod.sync("/reload", () => {
|
||||||
mod.reload();
|
mod.reload();
|
||||||
return "reload";
|
return "reload";
|
||||||
|
|
@ -329,22 +330,31 @@ function init(mod) {
|
||||||
mod.static("/meta/", "web/meta");
|
mod.static("/meta/", "web/meta");
|
||||||
mod.static("/kiri/", "web/kiri");
|
mod.static("/kiri/", "web/kiri");
|
||||||
|
|
||||||
// load modules
|
function load_modules(root, force) {
|
||||||
lastmod(`${dir}/mod`) && fs.readdirSync(`${dir}/mod`).forEach(dir => {
|
// load modules
|
||||||
const modpath = `mod/${dir}`;
|
lastmod(`${dir}/${root}`) && fs.readdirSync(`${dir}/${root}`).forEach(mdir => {
|
||||||
if (dir.charAt(0) === '.') return;
|
const modpath = `${root}/${mdir}`;
|
||||||
const stats = fs.lstatSync(`${mod.dir}/${modpath}`);
|
if (dir.charAt(0) === '.') return;
|
||||||
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
|
const stats = fs.lstatSync(`${mod.dir}/${modpath}`);
|
||||||
const isDebugMod = util.isfile(PATH.join(mod.dir,modpath,".debug"));
|
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
|
||||||
const isElectronMod = util.isfile(PATH.join(mod.dir,modpath,".electron"));
|
if (util.isfile(PATH.join(mod.dir,modpath,".disable"))) return;
|
||||||
if (ENV.electron && !isElectronMod) return;
|
const isDebugMod = util.isfile(PATH.join(mod.dir,modpath,".debug"));
|
||||||
if (!ENV.electron && isElectronMod && !isDebugMod) return;
|
const isElectronMod = util.isfile(PATH.join(mod.dir,modpath,".electron"));
|
||||||
try {
|
if (force || (ENV.electron && !isElectronMod)) return;
|
||||||
loadModule(mod, modpath);
|
if (force || (!ENV.electron && isElectronMod && !isDebugMod)) return;
|
||||||
} catch (error) {
|
try {
|
||||||
console.log({ module: dir, error });
|
loadModule(mod, modpath);
|
||||||
}
|
} catch (error) {
|
||||||
});
|
console.log({ module: mdir, error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// load development and 3rd party modules
|
||||||
|
load_modules('mod');
|
||||||
|
|
||||||
|
// load optional local modules
|
||||||
|
load_modules('mods');
|
||||||
|
|
||||||
// run loads injected by modules
|
// run loads injected by modules
|
||||||
while (load.length) {
|
while (load.length) {
|
||||||
|
|
|
||||||
0
mods/bambu/.debug
Normal file
0
mods/bambu/.debug
Normal file
0
mods/bambu/.electron
Normal file
0
mods/bambu/.electron
Normal file
80
mods/bambu/bambu.js
Normal file
80
mods/bambu/bambu.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
self.kiri.load(api => {
|
||||||
|
|
||||||
|
if (api.electron || api.const.LOCAL) {
|
||||||
|
console.log('BAMBU MODULE RUNNING');
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { kiri, moto } = self;
|
||||||
|
const { ui } = kiri;
|
||||||
|
const { local } = data;
|
||||||
|
const defhost = ";; DEFINE BAMBU-HOST ";
|
||||||
|
const defams = ";; DEFINE BAMBU-AMS ";
|
||||||
|
|
||||||
|
let init = false;
|
||||||
|
let host, password, serial, amsmap;
|
||||||
|
|
||||||
|
api.event.on("init-done", function() {
|
||||||
|
if (init) return;
|
||||||
|
init = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendok(params = {}) {
|
||||||
|
host = password = serial = amsmap = undefined;
|
||||||
|
const { settings } = params;
|
||||||
|
const ams = settings.device?.gcodePre.filter(line => line.indexOf(defams) === 0)[0];
|
||||||
|
if (ams) {
|
||||||
|
try {
|
||||||
|
amsmap = ams.substring(defams.length).trim().replaceAll(' ','');
|
||||||
|
} catch (e) {
|
||||||
|
console.log({ invalid_ams_map: ams });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const feature = settings.device?.gcodePre.filter(line => line.indexOf(defhost) === 0)[0];
|
||||||
|
if (feature) {
|
||||||
|
[ host, password, serial ] = feature.substring(defhost.length).split(' ').map(v => v.trim());
|
||||||
|
console.log('BAMBU', { host , password, serial });
|
||||||
|
if (host && password) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(filename, gcode) {
|
||||||
|
const baseUrl = '/api/bambu_send';
|
||||||
|
const url = new URL(baseUrl, window.location.origin);
|
||||||
|
url.searchParams.append('host', host);
|
||||||
|
url.searchParams.append('password', password);
|
||||||
|
url.searchParams.append('filename', filename);
|
||||||
|
url.searchParams.append('serial', serial);
|
||||||
|
url.searchParams.append('ams', amsmap);
|
||||||
|
|
||||||
|
const alert = api.alerts.show('Sending to Bambu Printer');
|
||||||
|
|
||||||
|
fetch(url.toString(), {
|
||||||
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
|
method: 'POST',
|
||||||
|
body: gcode
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
api.alerts.hide(alert);
|
||||||
|
return response.json();
|
||||||
|
}).then(res => {
|
||||||
|
console.log('Bambu Send', res);
|
||||||
|
if (res.sent) {
|
||||||
|
api.alerts.show('File Sent', 3);
|
||||||
|
} else {
|
||||||
|
api.alerts.show('File Send Error', 3);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Bambu Send Error', error);
|
||||||
|
api.alerts.show('File Send Error', 3);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
api.bambu = { send, sendok, amsmap };
|
||||||
|
});
|
||||||
178
mods/bambu/cli.js
Normal file
178
mods/bambu/cli.js
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
const [ host, pass, sn ] = process.argv.slice(2);
|
||||||
|
const util = require('util');
|
||||||
|
const mqtt = require("mqtt");
|
||||||
|
const readline = require('readline');
|
||||||
|
const reportTopic = `device/${sn}/report`;
|
||||||
|
const requestTopic = `device/${sn}/request`;
|
||||||
|
|
||||||
|
console.log({ host, pass, sn, reportTopic, requestTopic });
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
prompt: '> '
|
||||||
|
});
|
||||||
|
|
||||||
|
function erasePrompt() {
|
||||||
|
readline.clearLine(process.stdout, 0);
|
||||||
|
readline.cursorTo(process.stdout, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let amsmap = [];
|
||||||
|
|
||||||
|
rl.prompt();
|
||||||
|
|
||||||
|
rl.on('line', (line) => {
|
||||||
|
line = line.trim();
|
||||||
|
switch (line) {
|
||||||
|
case 'quit':
|
||||||
|
case 'exit':
|
||||||
|
log('Exiting...');
|
||||||
|
rl.close();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (line.startsWith('M') || line.startsWith('G') || line.startsWith('T')) {
|
||||||
|
line = line.split(';').map(l => l.trim()).join('\n');
|
||||||
|
sendGcode(line);
|
||||||
|
} else if (line.startsWith('ams ')) {
|
||||||
|
amsmap = JSON.parse(line.substring(4));
|
||||||
|
log({ amsmap });
|
||||||
|
} else if (line.startsWith('print ')) {
|
||||||
|
printStart(line.slice(6));
|
||||||
|
} else if (line === 'stop') {
|
||||||
|
printStop();
|
||||||
|
} else if (line === 'pause') {
|
||||||
|
printPause();
|
||||||
|
} else if (line === 'resume') {
|
||||||
|
printResume();
|
||||||
|
} else if (line.startsWith('{')) {
|
||||||
|
let cmd = eval('(' + line + ')');
|
||||||
|
log('sending', cmd);
|
||||||
|
sendRequest(cmd);
|
||||||
|
} else if (line) {
|
||||||
|
log('invalid command:', line);
|
||||||
|
}
|
||||||
|
rl.prompt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}).on('close', () => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
protocol: 'mqtts',
|
||||||
|
host: host,
|
||||||
|
port: 8883,
|
||||||
|
username: 'bblp',
|
||||||
|
password: pass,
|
||||||
|
// ca: fs.readFileSync('ca.crt'),
|
||||||
|
// cert: fs.readFileSync('client.crt'),
|
||||||
|
// key: fs.readFileSync('client.key'),
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
erasePrompt();
|
||||||
|
console.log(
|
||||||
|
[...arguments]
|
||||||
|
.map(v => util.inspect(v, {
|
||||||
|
maxArrayLength: null,
|
||||||
|
breakLength: this.break,
|
||||||
|
colors: true,
|
||||||
|
compact: true,
|
||||||
|
sorted: true,
|
||||||
|
depth: Infinity
|
||||||
|
}))
|
||||||
|
.join(this.join)
|
||||||
|
);
|
||||||
|
rl.prompt(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRequest(obj) {
|
||||||
|
client.publish(requestTopic, JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendGcode(gcode) {
|
||||||
|
sendRequest({
|
||||||
|
print: {
|
||||||
|
command: "gcode_line",
|
||||||
|
param: gcode,
|
||||||
|
sequence_id: "0"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printStart(file) {
|
||||||
|
log('print', { file });
|
||||||
|
sendRequest({
|
||||||
|
print: {
|
||||||
|
command: "project_file",
|
||||||
|
sequence_id: "0",
|
||||||
|
url: `file:///sdcard/${file}`,
|
||||||
|
param: "Metadata/plate_1.gcode",
|
||||||
|
subtask_id: "0",
|
||||||
|
use_ams: amsmap.length ? true : false,
|
||||||
|
timelapse: false,
|
||||||
|
flow_cali: false,
|
||||||
|
bed_leveling: false,
|
||||||
|
layer_inspect: false,
|
||||||
|
vibration_cali: false,
|
||||||
|
ams_mapping: amsmap || []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printStop() {
|
||||||
|
sendRequest({
|
||||||
|
print: {
|
||||||
|
command: "stop",
|
||||||
|
sequence_id: "0"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printPause() {
|
||||||
|
sendRequest({
|
||||||
|
print: {
|
||||||
|
command: "pause",
|
||||||
|
sequence_id: "0"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printResume() {
|
||||||
|
sendRequest({
|
||||||
|
print: {
|
||||||
|
command: "resume",
|
||||||
|
sequence_id: "0"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = mqtt.connect(options);
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
log("mqtt connected");
|
||||||
|
client.subscribe(reportTopic, (err) => {
|
||||||
|
if (err) {
|
||||||
|
log('mqtt subscribe error', err);
|
||||||
|
} else {
|
||||||
|
log('mqtt subscribed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("message", (topic, message) => {
|
||||||
|
log(JSON.parse(message.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("close", () => {
|
||||||
|
log("mqtt disconnect");
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
log('SIGINT. Running cleanup...');
|
||||||
|
client.end();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
177
mods/bambu/init.js
Normal file
177
mods/bambu/init.js
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
const { Client } = require('basic-ftp');
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
|
module.exports = async (server) => {
|
||||||
|
|
||||||
|
const { api, env, util } = server;
|
||||||
|
const confdir = util.confdir();
|
||||||
|
|
||||||
|
let client, topic_report, topic_request, timer;
|
||||||
|
|
||||||
|
const mqtt = require("mqtt");
|
||||||
|
const mqtt_options = {
|
||||||
|
protocol: 'mqtts',
|
||||||
|
port: 8883,
|
||||||
|
username: 'bblp',
|
||||||
|
// ca: fs.readFileSync('ca.crt'),
|
||||||
|
// key: fs.readFileSync('client.key'),
|
||||||
|
// cert: fs.readFileSync('client.crt'),
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mqtt_fn = {
|
||||||
|
async send(msg) {
|
||||||
|
if (client) {
|
||||||
|
util.log('mqtt_send', msg);
|
||||||
|
client.publish(topic_request, JSON.stringify(msg));
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => { mqtt_fn.end() }, 30000);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
end() {
|
||||||
|
if (client) {
|
||||||
|
util.log('mqtt end');
|
||||||
|
client.end();
|
||||||
|
client = undefined;
|
||||||
|
}
|
||||||
|
topic_report = undefined;
|
||||||
|
topic_request = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function get_mqtt(serial) {
|
||||||
|
const fns = {};
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
Object.assign(fns, { resolve, reject });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
return mqtt_fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = mqtt.connect(mqtt_options);
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
topic_report = `device/${serial}/report`;
|
||||||
|
topic_request = `device/${serial}/request`;
|
||||||
|
util.log({ topic_report, topic_request });
|
||||||
|
client.subscribe(topic_report, (err) => {
|
||||||
|
util.log('mqtt_subscribed', err);
|
||||||
|
fns.resolve(mqtt_fn);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("message", (topic, message) => {
|
||||||
|
util.log('mqtt_recv', JSON.parse(message.toString()));
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ftp_send(args = {}) {
|
||||||
|
const client = new Client();
|
||||||
|
const port = parseInt(args.port || 990);
|
||||||
|
const host = args.host || "localhost";
|
||||||
|
const user = args.user || "bblp";
|
||||||
|
const password = args.password || '';
|
||||||
|
const filename = args.filename || "test.3mf";
|
||||||
|
const data = args.data || undefined;
|
||||||
|
// client.ftp.verbose = true;
|
||||||
|
try {
|
||||||
|
await client.access({
|
||||||
|
port,
|
||||||
|
host,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
secure: "implicit",
|
||||||
|
secureOptions: { rejectUnauthorized: false }
|
||||||
|
});
|
||||||
|
const readableStream = new Readable();
|
||||||
|
readableStream._read = () => {};
|
||||||
|
readableStream.push(data);
|
||||||
|
readableStream.push(null);
|
||||||
|
await client.uploadFrom(readableStream, filename);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode_post(req, res, next) {
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
let chunks = [];
|
||||||
|
req
|
||||||
|
.on('data', data => chunks.push(data) )
|
||||||
|
.on('end', () => {
|
||||||
|
req.app.post = Buffer.concat(chunks);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert script before all others in kiri client
|
||||||
|
server.inject("kiri", "bambu.js");
|
||||||
|
|
||||||
|
function o2s(obj) {
|
||||||
|
return JSON.stringify(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(env.debug || env.electron)) {
|
||||||
|
util.log('no valid context for bambu');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.bambu_send = (req, res, next) => {
|
||||||
|
const { app, url, headers } = req;
|
||||||
|
const { host } = headers;
|
||||||
|
const { query } = app;
|
||||||
|
server.handler.addCORS(req, res);
|
||||||
|
decode_post(req, res, async () => {
|
||||||
|
res.setHeader("Content-Type", "application/octet-stream");
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, no-store, private');
|
||||||
|
const data = req.app.post;
|
||||||
|
const { host, password, filename, serial, ams } = query;
|
||||||
|
const ams_mapping = ams ? ams.split(',').map(v => parseInt(v)) : undefined;
|
||||||
|
const mqtt_conn = serial ? get_mqtt(serial) : undefined;
|
||||||
|
ftp_send({ host, password, filename, data })
|
||||||
|
.then(() => {
|
||||||
|
if (serial) {
|
||||||
|
Object.assign(mqtt_options, { host, password });
|
||||||
|
const cmd = {
|
||||||
|
print: {
|
||||||
|
command: "project_file",
|
||||||
|
url: `file:///sdcard/${filename}`,
|
||||||
|
param: "Metadata/plate_1.gcode",
|
||||||
|
subtask_id: "0",
|
||||||
|
use_ams: ams_mapping ? true : false,
|
||||||
|
timelapse: false,
|
||||||
|
flow_cali: false,
|
||||||
|
bed_leveling: false,
|
||||||
|
layer_inspect: false,
|
||||||
|
vibration_cali: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (ams_mapping) {
|
||||||
|
cmd.print.ams_mapping = ams_mapping;
|
||||||
|
}
|
||||||
|
mqtt_conn
|
||||||
|
.then(mqtt => mqtt.send(cmd))
|
||||||
|
.catch(err => {
|
||||||
|
util.log({ mqtt_err: err });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.end(o2s({ sent: true }));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
util.log({ ftp_send_error: error });
|
||||||
|
res.end(o2s({ sent: false, error }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
15
mods/package.json
Normal file
15
mods/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "grid-apps-mods",
|
||||||
|
"version": "4.1.0",
|
||||||
|
"description": "grid.space app modules",
|
||||||
|
"author": "Stewart Allen <sa@grid.space>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/gridspace/apps.git"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"basic-ftp": "^5.0.5",
|
||||||
|
"mqtt": "^5.10.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue