← Export CSV Logs to USB from a Node-RED Dashboard
Industrial mixing (touch HMI) TouchBerry Pi Ficheros Datalogging
Export CSV Logs to USB from a Node-RED Dashboard — full example One-tap USB export for industrial data logs: detect a pendrive on Raspberry Pi, copy the CSV history and confirm on screen, all from Node-RED.
Complete, runnable program for the TouchBerry Pi (export-history-usb.js): wiring header, requirements and integration notes included.
Read-only preview.
/*
* COMPLETE EXAMPLE — Exporting the activation history to a USB drive
*
* 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)
* - Raspberry Pi OS with USB auto-mount at /media/pi/
* - Activation CSV at /home/pi/logs/log_activaciones.csv
*
* Architecture:
* The operator presses "Export to USB" on the /history page. The flow
* detects whether a USB stick is mounted, copies the CSV with a dated
* name and confirms (or reports the error) with an on-screen notification.
*
* Flow wiring:
*
* [ui-button "Export to USB"]
* └─> [exec 1: ls /media/pi] (detect USB stick)
* └─(stdout)─> [function 1: chooseDestination]
* ├─(output 1: USB found)──> [exec 2: cp ...] (command in msg.payload)
* │ ├─(stdout)──> [function 2: notifyOk] ─> [green ui-notification]
* │ └─(stderr)──> [function 3: notifyError] ─> [red ui-notification]
* └─(output 2: no USB)──> [ui-notification "Insert a USB drive"]
*
* Node configuration:
* - exec 1: fixed command "ls /media/pi", append payload disabled.
* - exec 2: empty command, "append msg.payload" enabled (the full
* command arrives already built by function 1).
*/
// ============================================================
// function 1: chooseDestination
// ------------------------------------------------------------
// Receives the stdout of "ls /media/pi". Each line is a mount
// point (a USB drive). If there is one, it builds the copy
// command with a dated name; otherwise it warns the operator.
// Output 1: cp command ready / Output 2: no USB
// ============================================================
const SOURCE = "/home/pi/logs/log_activaciones.csv";
const devices = (msg.payload || "")
.split("\n")
.map(d => d.trim())
.filter(d => d.length > 0);
if (devices.length === 0) {
msg.payload = "Insert a USB drive and try again";
return [null, msg];
}
// Use the first mounted device; quotes in case the USB
// label contains spaces.
const destDir = "/media/pi/" + devices[0];
// Dated file name: log_activaciones_2026-06-12.csv
const today = new Date().toISOString().slice(0, 10);
const destination = destDir + "/log_activaciones_" + today + ".csv";
// 'sync' guarantees the data is written before the operator
// pulls out the USB stick.
msg.payload = `cp ${SOURCE} "${destination}" && sync`;
msg.destination = destination; // reused by the notification
return [msg, null];
// ============================================================
// function 2: notifyOk (stdout of exec 2)
// ------------------------------------------------------------
// cp prints nothing when everything goes well: we confirm ourselves.
// ============================================================
msg.payload = "History exported to " + (msg.destination || "the USB drive") +
". You can now remove the device.";
return msg;
// ============================================================
// function 3: notifyError (stderr of exec 2)
// ------------------------------------------------------------
// Typical errors: read-only USB, no space left, removed
// mid-copy. We show the raw error for diagnosis.
// ============================================================
msg.payload = "Export error: " + msg.payload;
node.error("USB export failed: " + msg.payload, msg);
return msg;