// 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));