← CSV Logging with a Live History Table in Node-RED
Industrial mixing (touch HMI)TouchBerry PiFicherosDatalogging
CSV Logging with a Live History Table in Node-RED — full example
Log machine activations to a CSV file and show them in a live Node-RED dashboard table using a watch node — simple, auditable datalogging on a PLC panel.
Complete, runnable program for the TouchBerry Pi (activation-log-csv-table.js): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — Activation logging to CSV with a live history table
*
* Hardware: TouchBerry Pi (Raspberry Pi + Industrial Shields PLC, touch screen)
* Based on: industrial mixing project (Node-RED touch HMI)
*
* Requirements:
* - Node-RED with @flowfuse/node-red-dashboard (UI v2) and the ui-table node
* - Directory /home/pi/logs existing and writable by Node-RED
*
* Architecture:
* Every time the operator activates the machine from the HMI, a line
* (date, user, reason, comment) is appended to the CSV. A watch node
* monitors the file: as soon as it changes, it is re-read, parsed and
* the table on the /history page is refreshed. This way the table always
* reflects the real file, which is the same one exported to USB.
*
* Flow wiring:
*
* -- Write branch --
* [activation form/button] ──> [function 1: formatCsvLine] ──> [file (append, no extra newline)]
*
* -- Read branch (live table) --
* [watch /home/pi/logs/log_activaciones.csv] ──> [read file (utf8, whole file)]
* ──> [function 2: csvToTable] ──> [ui-table "Activation history"]
*
* -- Initial load at startup --
* [inject once=true] ──> [read file] ──> [function 2] ──> [ui-table]
*
* Node configuration:
* - file: "append to file" mode, "add newline" DISABLED (the line already
* carries its own \n), filename set by msg.filename.
* - watch: path of the CSV; emits one msg per modification.
* - ui-table: columns Date / User / Reason / Comment.
*/
// ============================================================
// function 1: formatCsvLine
// ------------------------------------------------------------
// Input (from the dashboard activation form):
// msg.topic active user on the HMI
// msg.payload.reason reason selected in the dropdown
// msg.payload.comment free-text comment (optional)
// ============================================================
const d = new Date();
const pad = n => String(n).padStart(2, "0");
const formattedDate =
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
// Sanitize: the separator is the comma, so remove it from free-text fields
const sanitize = t => String(t || "").replace(/,/g, ";").replace(/\r?\n/g, " ");
const reason = sanitize(msg.payload.reason);
const comment = sanitize(msg.payload.comment);
msg.payload = formattedDate + "," + msg.topic + "," + reason + "," + comment + "\n";
msg.filename = "/home/pi/logs/log_activaciones.csv"; // file node in append mode
return msg;
// ============================================================
// function 2: csvToTable
// ------------------------------------------------------------
// Receives the full CSV (string) read by the read-file node and
// converts it into the array of objects expected by ui-table.
// The most recent activations are shown first.
// ============================================================
const MAX_ROWS = 200; // do not overload the HMI table
const lines = (msg.payload || "")
.split("\n")
.map(l => l.trim())
.filter(l => l.length > 0);
const rows = lines.map(line => {
const [date, user, reason, ...rest] = line.split(",");
return {
Date: date || "",
User: user || "",
Reason: reason || "",
Comment: rest.join(",") // the comment may contain ';'
};
});
msg.payload = rows.reverse().slice(0, MAX_ROWS);
return msg;
Download the full project pack — freeThis example + the related ones + bill of materials