112 lines
3.6 KiB
JavaScript
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));
|