Each printer now has one MQTT connection that stays open, merging the partial reports printers send while printing. Cards poll every 3 seconds (paused while the tab is hidden) and show state, file, progress with time left and finish time, layer, nozzle/bed/chamber temperatures, speed setting, and print error and HMS codes. A spool change syncs the Filament page and the card's drawing within seconds; a full report is requested every 10 minutes as a backstop, replacing the 10-minute probe loop. "Test & sync" uses the open connection rather than making a second one (?debug=1 still makes a short separate one). Connections are reconciled when printers are added, edited or removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
4.6 KiB
JavaScript
94 lines
4.6 KiB
JavaScript
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const printer = require('../lib/printer');
|
||
|
||
test('AMS trays are read into material, colour and how much is left', () => {
|
||
const trays = printer.describeAms([{ id: '0', tray: [
|
||
{ id: '0', tray_type: 'PLA', tray_color: 'FFFFFFFF', tray_info_idx: 'GFA00', tray_sub_brands: 'PLA Basic', remain: 82 },
|
||
{ id: '1', tray_type: '', tray_color: '00000000' }, // empty slot
|
||
{ id: '2', tray_type: 'PETG', tray_color: '000000FF', remain: -1 }, // not a Bambu spool
|
||
] }]);
|
||
assert.deepEqual(trays[0], {
|
||
unit: 0, slot: 0, empty: false, material: 'PLA', colour: '#FFFFFF',
|
||
profile: 'GFA00', brand: 'PLA Basic', remain: 82, uuid: null, tagUid: null, idName: null,
|
||
weight: null, type: 'Basic',
|
||
});
|
||
assert.equal(trays[1].empty, true);
|
||
assert.equal(trays[1].material, null);
|
||
assert.equal(trays[1].colour, null, 'an empty slot has no colour, whatever the printer says');
|
||
assert.equal(trays[2].remain, null, '-1 means the printer can’t tell');
|
||
assert.equal(trays[2].colour, '#000000', 'the alpha byte is dropped');
|
||
});
|
||
|
||
test('a Bambu spool’s RFID identity is kept, and an unread tag is not invented', () => {
|
||
const [tagged] = printer.describeAms([{ id: '0', tray: [{
|
||
id: '0', tray_type: 'PLA', tray_color: 'C12E1FFF',
|
||
tray_uuid: 'A1B2C3D4E5F60718293A4B5C6D7E8F90', tag_uid: '04AB12CD34EF5680', tray_id_name: 'A00-R0',
|
||
}] }]);
|
||
assert.equal(tagged.uuid, 'A1B2C3D4E5F60718293A4B5C6D7E8F90');
|
||
assert.equal(tagged.tagUid, '04AB12CD34EF5680');
|
||
assert.equal(tagged.idName, 'A00-R0');
|
||
|
||
const [plain] = printer.describeAms([{ id: '0', tray: [{
|
||
id: '0', tray_type: 'PLA', tray_color: 'FFFFFFFF',
|
||
tray_uuid: '00000000000000000000000000000000', tag_uid: '0000000000000000',
|
||
}] }]);
|
||
assert.equal(plain.uuid, null, 'all zeros means no tag was read');
|
||
assert.equal(plain.tagUid, null);
|
||
assert.equal(plain.idName, null);
|
||
});
|
||
|
||
test('a printer with no AMS reports nothing rather than failing', () => {
|
||
assert.deepEqual(printer.describeAms(undefined), []);
|
||
assert.deepEqual(printer.describeAms([{ id: '0' }]), []);
|
||
});
|
||
|
||
test('the external spool holder is read the same way as a tray', () => {
|
||
const ext = printer.describeTray({ id: '254', tray_type: 'PLA', tray_color: 'C12E1FFF', remain: 40 });
|
||
assert.equal(ext.unit, null);
|
||
assert.equal(ext.material, 'PLA');
|
||
assert.equal(ext.colour, '#C12E1F');
|
||
assert.equal(ext.remain, 40);
|
||
});
|
||
|
||
test('connection failures are explained in plain words', () => {
|
||
assert.equal(printer.friendlyError(new Error('Connection refused: Not authorized')),
|
||
'The printer refused the access code.');
|
||
assert.equal(printer.friendlyError(new Error('connect ECONNREFUSED 192.0.2.10:8883')),
|
||
'Nothing answered on port 8883 at that address.');
|
||
assert.equal(printer.friendlyError(new Error('connect EHOSTUNREACH 192.0.2.10:8883')),
|
||
'Couldn’t reach the printer from the server.');
|
||
assert.equal(printer.friendlyError(null), 'The connection failed.');
|
||
});
|
||
|
||
test('the product line is what follows the material in the brand', () => {
|
||
assert.equal(printer.productLine('PLA Basic', 'PLA'), 'Basic');
|
||
assert.equal(printer.productLine('PLA Silk+', 'PLA'), 'Silk+');
|
||
assert.equal(printer.productLine('PETG HF', 'PETG'), 'HF');
|
||
assert.equal(printer.productLine('Generic', 'PLA'), 'Generic', 'a brand without the material is kept whole');
|
||
assert.equal(printer.productLine('', 'PLA'), null);
|
||
});
|
||
|
||
test('a tray reports its weight when new', () => {
|
||
const t = printer.describeTray({ id: '0', tray_type: 'PLA', tray_color: 'FFFFFFFF', tray_weight: '1000' });
|
||
assert.equal(t.weight, 1000);
|
||
assert.equal(printer.describeTray({ id: '0', tray_type: 'PLA', tray_weight: '0' }).weight, null);
|
||
});
|
||
|
||
test('status is read from a report, and fields a report leaves out stay out', () => {
|
||
const { readStatus } = require('../lib/printer');
|
||
const full = readStatus({
|
||
gcode_state: 'RUNNING', mc_percent: 42, mc_remaining_time: 73, subtask_name: 'Benchy',
|
||
layer_num: 80, total_layer_num: 210, nozzle_temper: 219.6, nozzle_target_temper: 220,
|
||
bed_temper: 59.8, bed_target_temper: 60, spd_lvl: 2, print_error: 0,
|
||
hms: [{ attr: 0x07002000, code: 0x00020001 }],
|
||
});
|
||
assert.deepEqual(full, {
|
||
state: 'RUNNING', progress: 42, remainingMin: 73, file: 'Benchy', layer: 80, totalLayers: 210,
|
||
nozzle: 219.6, nozzleTarget: 220, bed: 59.8, bedTarget: 60, speed: 'Standard', printError: null,
|
||
hms: ['0700_2000_0002_0001'],
|
||
});
|
||
// A delta with only the nozzle temperature says nothing about the rest.
|
||
assert.deepEqual(readStatus({ nozzle_temper: 200 }), { nozzle: 200 });
|
||
assert.equal(readStatus({ print_error: 117473298 }).printError, '07008012');
|
||
});
|