Printers move out of Settings into their own table and page: each has an optional nickname (the model is shown when blank), a model from the Bambu lineup with a line drawing that follows the selection, an AMS type suggested from the model, and its address, serial and access code. Access codes are never returned to the browser. The printer set up in Settings is migrated automatically and its settings rows removed. Background sync covers every printer. Grams left are filled in only from an AMS that measures (AMS, AMS 2 Pro, AMS HT), never from an AMS lite. Spools now show their RFID tag ID, product code and filament code, and say which printer they're loaded in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
5.3 KiB
JavaScript
98 lines
5.3 KiB
JavaScript
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const { planSync, slotKey } = require('../lib/spoolSync');
|
||
const { paletteMatch } = require('../lib/filaments');
|
||
const palette = require('../db/bambu-filaments.json').filaments;
|
||
|
||
const SERIAL = 'PRINTER1';
|
||
const tray = (slot, hex, uuid, extra = {}) => ({
|
||
unit: 0, slot, empty: false, material: 'PLA', type: 'Basic', colour: hex, brand: 'PLA Basic',
|
||
uuid, tagUid: uuid ? `TAG${slot}` : null, idName: null, weight: 1000, ...extra,
|
||
});
|
||
|
||
test('a tagged spool the shelf has never seen is created and named from the filament list', () => {
|
||
const { actions, untracked } = planSync([], [tray(0, '#C12E1F', 'U-RED', { idName: 'A00-R0' })], SERIAL, palette);
|
||
assert.equal(untracked.length, 0);
|
||
assert.equal(actions.length, 1);
|
||
assert.equal(actions[0].op, 'create');
|
||
assert.equal(actions[0].slot, 'PRINTER1/0/0');
|
||
assert.equal(actions[0].spool.colour, 'Red');
|
||
assert.equal(actions[0].spool.code, '10200');
|
||
assert.equal(actions[0].spool.colour_hex, '#C12E1F');
|
||
assert.equal(actions[0].spool.product_code, 'A00-R0');
|
||
assert.equal(actions[0].spool.total_g, 1000);
|
||
assert.equal(actions[0].spool.remaining_g, null, 'an AMS lite can’t say how much is left');
|
||
});
|
||
|
||
test('a spool the shelf already knows is moved, not added again', () => {
|
||
const existing = [{ id: 7, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-BLACK',
|
||
printer_serial: SERIAL, ams_slot: 'PRINTER1/0/3' }];
|
||
const { actions } = planSync(existing, [tray(1, '#000000', 'U-BLACK')], SERIAL, palette);
|
||
assert.deepEqual(actions, [{ op: 'update', id: 7, slot: 'PRINTER1/0/1', printerSerial: SERIAL, remaining: null }]);
|
||
});
|
||
|
||
test('a spool added by hand is adopted by the printer rather than duplicated', () => {
|
||
// The Jade White spool added before the printer was connected.
|
||
const existing = [{ id: 1, material: 'PLA', colour_hex: '#ffffff', rfid_uuid: null, printer_serial: null, ams_slot: null }];
|
||
const { actions } = planSync(existing, [tray(3, '#FFFFFF', 'U-WHITE', { idName: 'A00-W1' })], SERIAL, palette);
|
||
assert.equal(actions.length, 1);
|
||
assert.equal(actions[0].op, 'adopt');
|
||
assert.equal(actions[0].id, 1);
|
||
assert.equal(actions[0].uuid, 'U-WHITE');
|
||
assert.equal(actions[0].code, '10100');
|
||
});
|
||
|
||
test('one hand-added spool is adopted once, even with two matching trays', () => {
|
||
const existing = [{ id: 1, material: 'PLA', colour_hex: '#FFFFFF', rfid_uuid: null }];
|
||
const { actions } = planSync(existing, [tray(0, '#FFFFFF', 'U-W1'), tray(1, '#FFFFFF', 'U-W2')], SERIAL, palette);
|
||
assert.deepEqual(actions.map((a) => a.op), ['adopt', 'create']);
|
||
});
|
||
|
||
test('a hand-added spool of another colour or material is left alone', () => {
|
||
const existing = [
|
||
{ id: 1, material: 'PLA', colour_hex: '#FF0000', rfid_uuid: null }, // wrong colour
|
||
{ id: 2, material: 'PETG', colour_hex: '#FFFFFF', rfid_uuid: null }, // wrong material
|
||
];
|
||
const { actions } = planSync(existing, [tray(0, '#FFFFFF', 'U-W')], SERIAL, palette);
|
||
assert.deepEqual(actions.map((a) => a.op), ['create']);
|
||
});
|
||
|
||
test('an untagged spool is listed, not guessed at', () => {
|
||
const { actions, untracked } = planSync([], [tray(2, '#123456', null)], SERIAL, palette);
|
||
assert.deepEqual(actions, []);
|
||
assert.deepEqual(untracked, [{ slot: 'PRINTER1/0/2', material: 'PLA', colour: '#123456' }]);
|
||
});
|
||
|
||
test('a spool this printer no longer reports is unloaded; other printers’ spools are not', () => {
|
||
const existing = [
|
||
{ id: 5, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-GONE', printer_serial: SERIAL, ams_slot: 'PRINTER1/0/0' },
|
||
{ id: 6, material: 'PLA', colour_hex: '#000000', rfid_uuid: 'U-ELSEWHERE', printer_serial: 'OTHER', ams_slot: 'OTHER/0/0' },
|
||
];
|
||
const { actions } = planSync(existing, [], SERIAL, palette);
|
||
assert.deepEqual(actions, [{ op: 'unload', id: 5 }]);
|
||
});
|
||
|
||
test('empty trays are ignored, and the external holder has its own slot key', () => {
|
||
assert.deepEqual(planSync([], [{ empty: true, unit: 0, slot: 0 }], SERIAL, palette).actions, []);
|
||
assert.equal(slotKey(SERIAL, { unit: null, slot: 254 }), 'PRINTER1/ext/254');
|
||
});
|
||
|
||
test('palette matching names a colour within its product line, and doesn’t guess', () => {
|
||
assert.deepEqual(paletteMatch(palette, 'PLA', 'Basic', '#FFFFFF'), { name: 'Jade White', code: '10100', type: 'Basic' });
|
||
assert.equal(paletteMatch(palette, 'PLA', 'Matte', '#FFFFFF').name, 'Ivory White');
|
||
assert.equal(paletteMatch(palette, 'PLA', 'Basic', '#123456'), null);
|
||
});
|
||
|
||
test('an AMS that measures fills in grams left; an AMS lite never does', () => {
|
||
const trays = [tray(0, '#C12E1F', 'U-RED', { remain: 42 })];
|
||
const lite = planSync([], trays, SERIAL, palette, { measures: false });
|
||
assert.equal(lite.actions[0].spool.remaining_g, null);
|
||
const pro = planSync([], trays, SERIAL, palette, { measures: true });
|
||
assert.equal(pro.actions[0].spool.remaining_g, 420);
|
||
|
||
const existing = [{ id: 7, material: 'PLA', colour_hex: '#C12E1F', rfid_uuid: 'U-RED', printer_serial: SERIAL, ams_slot: 'PRINTER1/0/0' }];
|
||
assert.equal(planSync(existing, trays, SERIAL, palette, { measures: true }).actions[0].remaining, 420);
|
||
// No reading at all leaves the stored amount alone.
|
||
const unknown = [tray(0, '#C12E1F', 'U-RED', { remain: null })];
|
||
assert.equal(planSync(existing, unknown, SERIAL, palette, { measures: true }).actions[0].remaining, null);
|
||
});
|