printhive/lib/printers.js
dlawler489 c2bae07e54 Show each printer's loaded filament on its card
Each sync keeps what the printer reported, slot by slot (material, colour,
empty, tagged — nothing that identifies a spool). The card's drawing colours
in the first AMS unit's spools, dashes empty slots, and lists every slot and
the external holder underneath with its colour name. "Test & sync" redraws
the card with what it just read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:21:03 +10:00

105 lines
3.8 KiB
JavaScript

// The printers you've added.
//
// Access codes are stored here but never handed back to the browser: every
// read returns `codeSet` instead, and saving with a blank code keeps the one
// already stored.
const db = require('./db');
// Which AMS types can say how much filament is left. An AMS lite only knows
// full or empty, so its reading is never used as a weight.
const AMS_TYPES = { none: false, ams_lite: false, ams: true, ams_2_pro: true, ams_ht: true };
const clean = (value, max) => {
const s = String(value ?? '').trim();
return s ? s.slice(0, max) : null;
};
function publicPrinter(row) {
if (!row) return null;
const { access_code: accessCode, ...rest } = row;
return { ...rest, codeSet: Boolean(accessCode) };
}
function fields(data) {
return {
nickname: clean(data.nickname, 60),
model: clean(data.model, 40),
host: clean(data.host, 255),
serial: clean(data.serial, 40),
ams_type: Object.prototype.hasOwnProperty.call(AMS_TYPES, data.ams_type) ? data.ams_type : null,
};
}
async function list() {
const { rows } = await db.query('SELECT * FROM printers ORDER BY id');
return rows.map(publicPrinter);
}
// Server-side only: includes the access code, for connecting.
async function listWithCodes() {
const { rows } = await db.query('SELECT * FROM printers ORDER BY id');
return rows;
}
async function get(id, { withCode = false } = {}) {
const { rows: [row] } = await db.query('SELECT * FROM printers WHERE id = $1', [id]);
if (!row) return null;
return withCode ? row : publicPrinter(row);
}
async function create(data) {
const f = fields(data);
const { rows: [row] } = await db.query(
`INSERT INTO printers (nickname, model, host, serial, access_code, ams_type)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
[f.nickname, f.model, f.host, f.serial, clean(data.access_code, 64), f.ams_type]);
return publicPrinter(row);
}
// Only the fields sent are changed; a blank access code keeps the stored one.
async function update(id, data) {
const f = fields(data);
const has = (key) => Object.prototype.hasOwnProperty.call(data, key);
const { rows: [row] } = await db.query(
`UPDATE printers SET
nickname = CASE WHEN $2::boolean THEN $3 ELSE nickname END,
model = CASE WHEN $4::boolean THEN $5 ELSE model END,
host = CASE WHEN $6::boolean THEN $7 ELSE host END,
serial = CASE WHEN $8::boolean THEN $9 ELSE serial END,
ams_type = CASE WHEN $10::boolean THEN $11 ELSE ams_type END,
access_code = COALESCE($12, access_code),
date_modified = now()
WHERE id = $1 RETURNING *`,
[id, has('nickname'), f.nickname, has('model'), f.model, has('host'), f.host,
has('serial'), f.serial, has('ams_type'), f.ams_type, clean(data.access_code, 64)]);
return publicPrinter(row);
}
// Removing a printer also marks its spools as no longer loaded anywhere.
async function remove(id) {
const { rows: [row] } = await db.query('DELETE FROM printers WHERE id = $1 RETURNING serial', [id]);
if (!row) return false;
if (row.serial) {
await db.query('UPDATE filament_spools SET ams_slot = NULL WHERE printer_serial = $1', [row.serial]);
}
return true;
}
// Keeps what a printer reported loaded — enough to draw its card, nothing that
// identifies a spool.
async function recordLoaded(id, trays) {
const loaded = (trays || []).map((t) => ({
unit: t.unit ?? null,
slot: t.slot ?? null,
empty: Boolean(t.empty),
material: t.material || null,
colour: t.colour || null,
tagged: Boolean(t.uuid),
}));
await db.query('UPDATE printers SET loaded = $2::jsonb, loaded_at = now() WHERE id = $1',
[id, JSON.stringify(loaded)]);
}
const measures = (amsType) => Boolean(AMS_TYPES[amsType]);
module.exports = { AMS_TYPES, list, listWithCodes, get, create, update, remove, recordLoaded, measures, publicPrinter };