radin-scale-api/server.js

112 lines
3.6 KiB
JavaScript

// server.js (CommonJS)
const express = require('express');
const cors = require('cors');
const { SerialPort } = require('serialport');
const API_PORT = Number(process.env.API_PORT || 8787);
const SILENCE_MS = Number(process.env.RADIN_SILENCE_MS || 50);
let portPath = process.env.RADIN_PORT || 'COM8';
let baudRate = Number(process.env.RADIN_BAUD || 19200);
let port = null;
let buffer = '';
let latestWeight = null;
let latestRaw = null;
let lastUpdateAt = null;
let silenceTimer = null;
function parseFrame(frame) {
if (!frame) return;
latestRaw = frame;
const nums = frame.replace(',', '.').match(/-?\d+(?:\.\d+)?/g);
if (!nums) return;
const v = parseFloat(nums[nums.length - 1]);
latestWeight = Math.round(v * 1000) / 1000;
lastUpdateAt = new Date();
}
function feedBufferAndParse(txt) {
buffer += txt;
// split on CR/LF/spaces; flush intermediate parts
const parts = buffer.split(/\r\n|\r|\n| +/);
if (parts.length > 1) {
buffer = parts.pop();
for (const p of parts) if (p.trim()) parseFrame(p.trim());
}
// also flush on short silence to catch continuous streams
clearTimeout(silenceTimer);
silenceTimer = setTimeout(() => {
const f = buffer.trim();
buffer = '';
if (f) parseFrame(f);
}, SILENCE_MS);
}
async function openSerial() {
if (port && port.isOpen) return;
port = new SerialPort({ path: portPath, baudRate, autoOpen: false });
port.on('error', e => console.log('[serial error]', e.message));
port.on('close', () => {
console.log('[serial] closed; retrying in 2s');
setTimeout(() => openSerial().catch(() => {}), 2000);
});
await new Promise((res, rej) => port.open(err => (err ? rej(err) : res())));
console.log(`[serial] opened ${portPath} @ ${baudRate}`);
port.on('data', buf => {
const txt = buf.toString('utf8');
// Uncomment for debugging raw input:
// console.log('[serial raw]', JSON.stringify(txt));
feedBufferAndParse(txt);
});
}
function closeSerial() {
try { clearTimeout(silenceTimer); } catch {}
if (port && port.isOpen) {
try { port.removeAllListeners('data'); } catch {}
try { port.close(); } catch {}
}
port = null;
}
const app = express();
app.use(cors());
app.use(express.json());
// ---- HTTP API ----
app.get('/status', (_req, res) => {
res.json({ connected: !!(port && port.isOpen), port: portPath, baudRate, lastUpdateAt });
});
app.get('/ports', async (_req, res) => {
const list = await SerialPort.list();
res.json(list.map(p => ({
path: p.path,
manufacturer: p.manufacturer || null,
vendorId: p.vendorId || null,
productId: p.productId || null
})));
});
app.post('/connect', async (req, res) => {
try {
if (req.body?.path) portPath = req.body.path;
if (req.body?.baud) baudRate = Number(req.body.baud) || baudRate;
closeSerial();
await openSerial();
res.json({ ok: true, port: portPath, baudRate });
} catch (e) { res.status(500).json({ ok: false, error: e.message }); }
});
app.post('/disconnect', (_req, res) => { closeSerial(); res.json({ ok: true }); });
app.get('/weight', (_req, res) => { res.json({ kg: latestWeight, updatedAt: lastUpdateAt }); });
app.get('/raw', (_req, res) => { res.json({ raw: latestRaw, updatedAt: lastUpdateAt }); });
app.listen(API_PORT, () => console.log(`[api] http://127.0.0.1:${API_PORT}`));
// Auto-open on start if RADIN_PORT is set
if (portPath) openSerial().catch(err => console.log('[serial open failed]', err.message));