2025-01-29 11:54:52 -05:00
|
|
|
const { Client } = require('@gridspace/basic-ftp');
|
2025-01-25 18:03:56 -05:00
|
|
|
const { Readable } = require('stream');
|
|
|
|
|
|
|
|
|
|
module.exports = async (server) => {
|
|
|
|
|
|
|
|
|
|
const { api, env, util } = server;
|
|
|
|
|
const confdir = util.confdir();
|
2025-01-28 09:54:00 -05:00
|
|
|
const mqtt = require("mqtt");
|
|
|
|
|
const mcache = {};
|
2025-01-28 12:45:35 -05:00
|
|
|
const wsopen = [];
|
2025-01-28 09:54:00 -05:00
|
|
|
|
|
|
|
|
class MQTT {
|
|
|
|
|
#timer;
|
|
|
|
|
#client;
|
|
|
|
|
#serial;
|
|
|
|
|
#topic_report;
|
|
|
|
|
#topic_request;
|
|
|
|
|
#options = {
|
|
|
|
|
protocol: 'mqtts',
|
|
|
|
|
port: 8883,
|
|
|
|
|
username: 'bblp',
|
|
|
|
|
// ca: fs.readFileSync('ca.crt'),
|
|
|
|
|
// key: fs.readFileSync('client.key'),
|
|
|
|
|
// cert: fs.readFileSync('client.crt'),
|
|
|
|
|
rejectUnauthorized: false
|
|
|
|
|
}
|
2025-01-25 18:03:56 -05:00
|
|
|
|
2025-01-28 12:45:35 -05:00
|
|
|
constructor(host, code, serial, onready, onerror, onmessage) {
|
2025-01-28 09:54:00 -05:00
|
|
|
this.#options.host = host;
|
|
|
|
|
this.#options.password = code;
|
|
|
|
|
this.#serial = serial;
|
|
|
|
|
let client = this.#client = mqtt.connect(this.#options);
|
|
|
|
|
|
|
|
|
|
client.on("connect", () => {
|
|
|
|
|
let report = this.#topic_report = `device/${serial}/report`;
|
|
|
|
|
let request = this.#topic_request = `device/${serial}/request`;
|
2025-01-28 12:45:35 -05:00
|
|
|
// util.log({ report, request });
|
2025-01-28 09:54:00 -05:00
|
|
|
client.subscribe(report, (err) => {
|
2025-01-29 11:05:02 -05:00
|
|
|
util.log('mqtt sub', this.#serial, err || "ok");
|
2025-01-28 09:54:00 -05:00
|
|
|
onready(this);
|
2025-01-28 12:45:35 -05:00
|
|
|
this.keepalive();
|
2025-01-28 09:54:00 -05:00
|
|
|
});
|
|
|
|
|
});
|
2025-01-25 18:03:56 -05:00
|
|
|
|
2025-01-28 09:54:00 -05:00
|
|
|
client.on("message", (topic, message) => {
|
2025-01-28 12:45:35 -05:00
|
|
|
message = JSON.parse(message.toString());
|
|
|
|
|
if (onmessage) {
|
|
|
|
|
onmessage(message);
|
|
|
|
|
} else {
|
|
|
|
|
util.log('mqtt_recv', this.#serial, message);
|
|
|
|
|
}
|
2025-01-28 09:54:00 -05:00
|
|
|
});
|
2025-01-28 12:45:35 -05:00
|
|
|
|
|
|
|
|
client.on("error", error => onerror(error));
|
2025-01-28 09:54:00 -05:00
|
|
|
}
|
|
|
|
|
|
2025-01-28 12:45:35 -05:00
|
|
|
keepalive() {
|
2025-01-28 09:54:00 -05:00
|
|
|
clearTimeout(this.#timer);
|
|
|
|
|
this.#timer = setTimeout(() => { this.end() }, 30000);
|
|
|
|
|
}
|
2025-01-25 18:03:56 -05:00
|
|
|
|
|
|
|
|
async send(msg) {
|
2025-01-28 09:54:00 -05:00
|
|
|
if (this.#client) {
|
2025-01-29 11:05:02 -05:00
|
|
|
util.log('mqtt send', this.#serial, msg);
|
2025-01-28 09:54:00 -05:00
|
|
|
this.#client.publish(this.#topic_request, JSON.stringify(msg));
|
2025-01-28 12:45:35 -05:00
|
|
|
this.keepalive();
|
2025-01-25 18:03:56 -05:00
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-01-28 09:54:00 -05:00
|
|
|
}
|
2025-01-25 18:03:56 -05:00
|
|
|
|
|
|
|
|
end() {
|
2025-01-28 09:54:00 -05:00
|
|
|
if (this.#client) {
|
2025-01-28 12:45:35 -05:00
|
|
|
util.log('mqtt end', this.#serial);
|
2025-01-28 09:54:00 -05:00
|
|
|
this.#client.end();
|
|
|
|
|
this.#client = undefined;
|
2025-01-25 18:03:56 -05:00
|
|
|
}
|
2025-01-28 09:54:00 -05:00
|
|
|
this.#topic_report = undefined;
|
|
|
|
|
this.#topic_request = undefined;
|
|
|
|
|
delete mcache[this.#serial];
|
2025-01-25 18:03:56 -05:00
|
|
|
}
|
2025-01-28 09:54:00 -05:00
|
|
|
}
|
2025-01-25 18:03:56 -05:00
|
|
|
|
2025-01-28 12:45:35 -05:00
|
|
|
function get_mqtt(host, code, serial, onmsg, onconn) {
|
2025-01-25 18:03:56 -05:00
|
|
|
const fns = {};
|
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
|
|
|
Object.assign(fns, { resolve, reject });
|
|
|
|
|
});
|
|
|
|
|
|
2025-01-28 09:54:00 -05:00
|
|
|
let mqtt = mcache[serial];
|
|
|
|
|
if (mqtt) {
|
2025-01-28 12:45:35 -05:00
|
|
|
fns.resolve(mqtt);
|
2025-01-28 09:54:00 -05:00
|
|
|
} else {
|
|
|
|
|
mqtt = new MQTT(host, code, serial, obj => {
|
|
|
|
|
mcache[serial] = obj;
|
|
|
|
|
fns.resolve(obj);
|
2025-01-28 12:45:35 -05:00
|
|
|
if (onconn) {
|
|
|
|
|
onconn(mqtt);
|
|
|
|
|
}
|
|
|
|
|
}, error => fns.reject(error), onmsg);
|
2025-01-25 18:03:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-29 00:35:56 -05:00
|
|
|
async function ftp_open(args = {}) {
|
2025-01-25 18:03:56 -05:00
|
|
|
const client = new Client();
|
|
|
|
|
const port = parseInt(args.port || 990);
|
|
|
|
|
const host = args.host || "localhost";
|
|
|
|
|
const user = args.user || "bblp";
|
2025-01-29 00:35:56 -05:00
|
|
|
const password = args.password || args.code || '';
|
2025-01-25 18:03:56 -05:00
|
|
|
// client.ftp.verbose = true;
|
|
|
|
|
try {
|
|
|
|
|
await client.access({
|
|
|
|
|
port,
|
|
|
|
|
host,
|
|
|
|
|
user,
|
|
|
|
|
password,
|
|
|
|
|
secure: "implicit",
|
|
|
|
|
secureOptions: { rejectUnauthorized: false }
|
|
|
|
|
});
|
2025-01-29 14:11:50 -05:00
|
|
|
} catch (error) {
|
2025-01-29 00:35:56 -05:00
|
|
|
util.log({ ftp_error: error });
|
2025-01-29 14:11:50 -05:00
|
|
|
throw error;
|
2025-01-29 00:35:56 -05:00
|
|
|
}
|
|
|
|
|
return client;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ftp_send(args = {}) {
|
|
|
|
|
const client = await ftp_open(args);
|
|
|
|
|
const filename = args.filename || "test.3mf";
|
|
|
|
|
const data = args.data || undefined;
|
|
|
|
|
try {
|
2025-01-25 18:03:56 -05:00
|
|
|
const readableStream = new Readable();
|
|
|
|
|
readableStream._read = () => {};
|
|
|
|
|
readableStream.push(data);
|
|
|
|
|
readableStream.push(null);
|
|
|
|
|
await client.uploadFrom(readableStream, filename);
|
|
|
|
|
} finally {
|
|
|
|
|
client.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-29 00:35:56 -05:00
|
|
|
async function ftp_list(args = {}) {
|
|
|
|
|
const client = await ftp_open(args);
|
|
|
|
|
const list = [];
|
2025-02-01 18:45:33 -05:00
|
|
|
try {
|
|
|
|
|
let files = await client.list();
|
|
|
|
|
files.forEach(file => file.path = "/");
|
|
|
|
|
list.push(...files);
|
|
|
|
|
} catch (e) { }
|
|
|
|
|
try {
|
|
|
|
|
let files = await client.list("/cache");
|
|
|
|
|
files.forEach(file => file.path = "/cache/");
|
|
|
|
|
list.push(...files);
|
|
|
|
|
} catch (e) { }
|
2025-02-01 01:36:56 -05:00
|
|
|
client.close();
|
2025-01-29 00:35:56 -05:00
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 18:03:56 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-29 14:11:50 -05:00
|
|
|
function wsend(msg) {
|
|
|
|
|
wsopen.forEach(ws => ws.send(JSON.stringify(msg)));
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 18:03:56 -05:00
|
|
|
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;
|
2025-01-28 12:45:35 -05:00
|
|
|
const mqtt_conn = serial ? get_mqtt(host, password, serial, message => {
|
2025-02-01 18:45:33 -05:00
|
|
|
util.log('mqtt_recv', message);
|
|
|
|
|
// util.log('mqtt_recv', JSON.parse(message.toString()));
|
2025-01-28 12:45:35 -05:00
|
|
|
}) : undefined;
|
2025-01-25 18:03:56 -05:00
|
|
|
ftp_send({ host, password, filename, data })
|
|
|
|
|
.then(() => {
|
|
|
|
|
if (serial) {
|
|
|
|
|
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 }));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-28 00:44:13 -05:00
|
|
|
server.ws.register("/bambu", function(ws, req) {
|
2025-01-28 12:45:35 -05:00
|
|
|
wsopen.push(ws);
|
2025-01-29 11:05:02 -05:00
|
|
|
util.log('ws open', req.url, wsopen.length);
|
2025-01-28 00:44:13 -05:00
|
|
|
ws.on('message', msg => {
|
|
|
|
|
msg = JSON.parse(msg);
|
2025-01-28 12:45:35 -05:00
|
|
|
let { cmd, host, code, serial } = msg;
|
|
|
|
|
switch (cmd) {
|
2025-01-28 00:44:13 -05:00
|
|
|
case "monitor":
|
2025-01-28 12:45:35 -05:00
|
|
|
get_mqtt(host, code, serial, message => {
|
|
|
|
|
// util.log({ mqtt_msg: serial });
|
2025-01-29 14:11:50 -05:00
|
|
|
wsend({ serial, message });
|
2025-01-28 12:45:35 -05:00
|
|
|
}, mqtt => {
|
|
|
|
|
// on open only
|
|
|
|
|
}).then(mqtt => {
|
|
|
|
|
// util.log({ mqtt_mon: mqtt });
|
|
|
|
|
// request all printer state info
|
|
|
|
|
mqtt.send({
|
|
|
|
|
pushing: {
|
|
|
|
|
sequence_id: "0",
|
|
|
|
|
command: "pushall"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// request system info
|
2025-01-28 16:37:44 -05:00
|
|
|
false && mqtt.send({
|
2025-01-28 12:45:35 -05:00
|
|
|
info: {
|
|
|
|
|
command: "get_version"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
util.log({ mqtt_err: error });
|
2025-01-29 14:11:50 -05:00
|
|
|
wsend({ serial, error: error.message || error.toString() });
|
2025-01-28 09:54:00 -05:00
|
|
|
});
|
2025-01-28 00:44:13 -05:00
|
|
|
break;
|
2025-01-29 00:35:56 -05:00
|
|
|
case "files":
|
|
|
|
|
ftp_list({ host, code }).then(files => {
|
2025-02-01 01:36:56 -05:00
|
|
|
util.log({ ftp_files: files.length });
|
|
|
|
|
// console.log(JSON.stringify(files,undefined,4));
|
2025-01-29 00:35:56 -05:00
|
|
|
files = files
|
|
|
|
|
.filter(file => file.name.toLowerCase().endsWith(".3mf"))
|
|
|
|
|
.map(file => {
|
|
|
|
|
return {
|
2025-02-01 18:45:33 -05:00
|
|
|
path: file.path,
|
2025-01-29 00:35:56 -05:00
|
|
|
name: file.name,
|
2025-02-01 01:36:56 -05:00
|
|
|
size: file.size,
|
|
|
|
|
date: file.rawModifiedAt
|
2025-01-29 00:35:56 -05:00
|
|
|
};
|
|
|
|
|
});
|
2025-01-29 14:11:50 -05:00
|
|
|
wsend({ serial, message: { files }});
|
|
|
|
|
}).catch(error => {
|
2025-02-01 01:36:56 -05:00
|
|
|
util.log({ ftp_error: error });
|
2025-01-29 14:11:50 -05:00
|
|
|
wsend({ serial, error: error.message || error.toString() });
|
2025-01-29 00:35:56 -05:00
|
|
|
});
|
|
|
|
|
break;
|
2025-02-01 01:36:56 -05:00
|
|
|
case "pause":
|
|
|
|
|
wsend({ print: { command: "pause", sequence_id: "0" } });
|
|
|
|
|
break;
|
|
|
|
|
case "resume":
|
|
|
|
|
wsend({ print: { command: "resume", sequence_id: "0" } });
|
|
|
|
|
break;
|
|
|
|
|
case "cancel":
|
|
|
|
|
wsend({ print: { command: "stop", sequence_id: "0", param: "" } });
|
|
|
|
|
break;
|
2025-01-28 12:45:35 -05:00
|
|
|
case "keepalive":
|
|
|
|
|
// util.log({ keepalive: serial });
|
|
|
|
|
mcache[serial]?.keepalive();
|
|
|
|
|
break;
|
2025-01-28 00:44:13 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
ws.on('close', () => {
|
2025-01-28 12:45:35 -05:00
|
|
|
let io = wsopen.indexOf(ws);
|
|
|
|
|
if (io >= 0) wsopen.splice(io, 1);
|
|
|
|
|
util.log('ws close', wsopen.length);
|
2025-01-28 00:44:13 -05:00
|
|
|
});
|
|
|
|
|
});
|
2025-01-25 18:03:56 -05:00
|
|
|
};
|