UNPKG

bambu-cli

Version:

Bambulabs CLI for printers

303 lines (278 loc) 10.6 kB
const cfg = require('./config.js'); const Table = require('easy-table'); const namer = require('color-namer'); const async = require('async'); const utils = require('./utils.js'); const ftp = require('./ftp.js'); const logger = require('./logger.js'); const CONSTS = require('./const.js'); const statusCheck = {}; module.exports = (args) => { let machines = cfg.get('machines'); if (args.id) { if (!args.json) { logger.log(`Showing only, ${args.id}`); } const m = cfg.getMachine(args.id); if (!m) { logger.error(`Failed to find machine: ${args.id}`); process.exit(2); } return showAll(args, [m]); } showAll(args, machines); }; const showAll = (args, _machines) => { const len = _machines.length; let machines = JSON.parse(JSON.stringify(_machines)); if (!args.json) { logger.log(`Checking connectivity for ${machines.length} machine(s)\n`); } async.each(machines, statusFTP, () => { machines = JSON.parse(JSON.stringify(_machines)); async.each(machines, statusMQTT, () => { logger.debug('statusCheck', JSON.stringify(statusCheck, null, 4)); const table = new Table(); let showExt = false; const slim = (len === 1) ? false : (args.slim || false); Object.keys(statusCheck).forEach(id => { const m = statusCheck[id]; if (m.external.color && m.external.type) { showExt = true; } }); Object.keys(statusCheck).forEach(id => { const m = statusCheck[id]; let ams = 'n/a'; if (m.ams && Array.isArray(m.ams.ams)) { ams = []; m.ams.ams.forEach((a) => { const l = utils.amsNumToLetter(a.id); ams.push(l); }); ams = ams.join(','); } let ext = 'n/a'; if (m.external.color && m.external.type) { const c = utils.colorBox(m.external.color, 1); ext = `${c} #${m.external.color}/${m.external.type}`; if (Number(m.ams.tray_now) === 254) { ext += '*'; } } if (!slim) { table.cell('ID', m.machine.id); } table.cell('Name', m.machine.name); if (!slim) { table.cell('IP Address', m.machine.ip); } table.cell('FTP', utils.status(m.ftp)); table.cell('MQTT', utils.status(m.mqtt)); if (showExt) { table.cell('Ext Spool', ext); } table.cell('AMS', ams); table.cell('Nozzle', m.nozzle); table.cell('Printing', m.printing); table.cell('Task', utils.truncate(m.task)); table.cell('Percent', m.percent); table.cell('Remaining', m.remaining); table.cell('Speed', m.speed); table.newRow(); }); utils.saveMachineMeta(statusCheck); if (args.json) { console.log(JSON.stringify(statusCheck, null, 4)); } else { logger.log(table.toString()); } if (len === 1 && !args.json) { //console.log(JSON.stringify(statusCheck, null, 4)); showDetails(); } if (!args.json) { console.log(); showErrors(); } }); }); }; const showErrors = () => { const single = Object.keys(statusCheck).length === 1 ? true : false; Object.values(statusCheck).forEach(machine => { if (machine.hms.length) { machine.hms.forEach(h => { const code = utils.hmsErrorToCode(h.attr, h.code); const str = utils.hmsErrorLookup(h.attr, h.code); const lvl = utils.hmsLevel(h.code); //console.log(code, str); let log = `[${code}] ${str}`; if (!single) { log = `[${machine.machine.id}]${log}`; } logger[lvl](log); logger.log(utils.hmsErrorURL(machine.machine, h.attr, h.code)); }); console.log(); } }); }; const showDetails = () => { const id = Object.keys(statusCheck)[0]; const machine = statusCheck[id]; //console.log(machine); //Show firmware update info.. if (machine.update && machine.update.ota_new_version_number) { logger.warn(`New Firmware detected: ${machine.update.ota_new_version_number}\n`); } const table = new Table(); table.cell('Model', machine.machine.make); machine.hardware.forEach(h => { if (h.sn === id) { table.cell('Machine firm', h.sw_ver); } if (h.name === 'ahb') { table.cell('AMS Hub firm', h.sw_ver); } if (h.name.startsWith('ams/')) { const l = utils.amsNumToLetter(h.name.split('/')[1]); table.cell(`AMS ${l} firm`, h.sw_ver); } }); table.newRow(); logger.log(table.toString()); if (machine.remainingStamp) { const stamp = (Date.now() + (machine.remainingStamp * 60) * 1000); console.log(`Estimated completion time: ${(new Date(stamp)).toLocaleString()}`); } if (machine.ams && machine.ams.ams) { logger.log(); const ams = machine.ams.ams; //console.log(JSON.stringify(machine.ams, null, 4)); const current = Number(machine.ams.tray_now); const table = new Table(); let tc = 0; ams.forEach(a => { const amsID = utils.amsNumToLetter(a.id); //console.log(`AMS ${amsID}: ${a.temp}°C`); const trays = a.tray; trays.forEach((tray) => { const id = Number(tray.id) + 1; let amsName = `${amsID} ${id}`; if (tc === current) { amsName += '*'; } table.cell('AMS', amsName); if (Object.keys(tray).length === 1) { //console.log(`Slot ${id} is empty!`); table.cell(`Color`, 'Empty'); table.cell('Color Name', 'Empty'); table.cell(`Hex`, ' '); table.cell('Type', 'Empty'); table.cell('Brand', 'Empty'); table.cell('Remain', `0%`); table.newRow(); return; } const color = tray.tray_color.substr(0, 6); if (tray.cols && tray.cols.length > 1) { //Mulicolor const color1 = tray.cols[0].substr(0, 6); const color2 = tray.cols[1].substr(0, 6); let colorBlock = utils.colorBox(color1, 2); colorBlock += utils.colorBox(color2, 2); table.cell(`Color`, colorBlock); table.cell('Color Name', `${getColorName(color1)}/${getColorName(color2)}`); table.cell(`Hex`, `#${color1}/#${color2}`); } else { table.cell(`Color`, utils.colorBox(color, 4)); table.cell('Color Name', getColorName(color)); table.cell(`Hex`, `#${color}`); } table.cell('Type', tray.tray_type); //table.cell('Brand', brand); table.cell('Brand', CONSTS.FILAMENT_NAMES[tray.tray_info_idx]); table.cell('Remain', `${tray.remain}%`); table.newRow(); tc++; }); }); logger.log(table.toString()); logger.log(`* denotes active filament`); logger.log(`Filament color names are in Pantone..`); } }; const getColorName = (hex) => { const options = { pick: ['basic', 'pantone'] }; //const n = namer(`#${hex}`, options).basic[0].name; const n = namer(`#${hex}`, options).pantone[0].name; //console.log(namer(`#${hex}`));process.exit(); //console.log(color, namer(`#${color}`, { pick: ['basic'] }).basic[0].name); return `${n.substr(0, 1).toUpperCase()}${n.substr(1)}`; }; const statusMQTT = (m, cb) => { statusCheck[m.id] = statusCheck[m.id] || {}; statusCheck[m.id].machine = m; statusCheck[m.id].external = { color: false, type: false }; statusCheck[m.id].ams = 'None'; statusCheck[m.id].mqtt = false; statusCheck[m.id].printing = false; statusCheck[m.id].task = 'None'; statusCheck[m.id].percent = 'n/a'; statusCheck[m.id].remaining = 'n/a'; statusCheck[m.id].speed = 'n/a'; const client = utils.mqttClient(m); client.on('error', () => { client.end(); cb(); }); client.on("connect", (e) => { statusCheck[m.id].mqtt = true; client.unsubscribe(`device/${m.id}/report`, () => {}); setTimeout(() => { client.subscribe(`device/${m.id}/report`, () => {}); CONSTS.MQTT_INIT.forEach((init) => { client.publish(`device/${m.id}/request`, JSON.stringify(init)); }); }, 500); }); const topics = { info: 0, print: 0 }; client.on('message', (topic, message) => { const json = JSON.parse(message.toString()); statusCheck[m.id] = utils.mqttMessage(json, statusCheck[m.id]); if (json.info) { topics.info++; } if (json.print) { topics.print++; } //Checking for more than a few messages to make sure we get all the //data that we need to continue. Takes a few seconds longer but is more reliable if (topics.print > 2 && topics.info > 0) { client.end(); cb(); } }); }; const statusFTP = async(m, cb) => { const alive = await checkFTP(m); statusCheck[m.id] = statusCheck[m.id] || {}; statusCheck[m.id].machine = m; statusCheck[m.id].ftp = alive; cb(); }; const checkFTP = async(machine) => { const client = await ftp.makeClient(machine); try { const list = await client.list('/'); await client.close(); return (list.length) ? true : false; } catch (e) { return false; } };