Modicon M241/M251 WebVisu: Trending CSV Data via HTTP Server

Claire Rousseau10 min read
HMI / SCADASchneider ElectricTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Modicon M241/M251 WebVisu: Trending CSV Data via HTTP Server

Schneider Electric Modicon M241 and M251 logic controllers expose a built-in WebVisu server, but the platform ships with only 128 MB of internal user memory. Any long-duration trend logging of photovoltaic arrays, process variables, or energy meters quickly exhausts this budget, so engineers must offload CSV archives to the SD card and serve them to a browser through a custom HTTP stack. This guide details a working architecture: a TCP-based HTTP server on the controller, the CAA File library for SD card streaming, JSON metadata exchange, and a dygraphs front-end polled once per second.

1. Problem Definition and Design Constraints

The WebVisu page shipped by EcoStruxure Machine Expert (formerly SoMachine) only renders scalar variables and internal trend buffers stored in RAM. The hard limits on the M241/M251 are:

Resource M241 M251
Internal user memory (/usr) 128 MB 128 MB
WebVisu page size budget Limited by /usr partition
External storage SD card (/sd0) SD card (/sd0)
CSV log retention (typical) Months-years Months-years
Embedded web trend viewer Not provided Not provided

Three architectural constraints drive every design decision in the sections that follow:

  1. WebVisu pages cannot natively load files outside /usr/visu.
  2. The browser's same-origin policy blocks AJAX calls from a page hosted on the WebVisu port unless the server returns Access-Control-Allow-Origin: *.
  3. Schneider does not provide a public HTTP server FB in the standard library; you build it on top of the TCP server sample or the OSCAT NET library.
The M241/M251 firmware cycle restricts the runtime to TLS 1.0/1.1 on most firmware revisions. Use plain HTTP for local visualization to avoid browser warnings.

2. System Architecture Overview

The solution chains four building blocks:

  1. TCP/HTTP server FB on the PLC, listening on a port distinct from the WebVisu HTTP port (for example 8080).
  2. CAA File library FB instances that copy CSV files from /sd0/ to /usr/visu/temp/ on demand.
  3. JSON metadata generator that produces graphconfig.json describing available files and their timestamps.
  4. Custom HTML page served from /usr/visu that iframes the WebVisu and uses AJAX to poll graphconfig.json plus the CSV streams from the same HTTP server.

Polling once per second keeps CPU load negligible while delivering near-real-time chart updates. In the field, copying four 56 KB CSVs from SD card to /usr/visu/temp/ completes in approximately one second on an M251 with the CAA File library.

3. Prerequisites

Item Required Version / Part Notes
Controller TM241CE40R / TM251MESC (or equivalent M241/M251) Must support WebVisu option
Firmware OS firmware supporting CAA File library Verify in EcoStruxure Machine Expert device catalog
Programming software EcoStruxure Machine Expert (formerly SoMachine) V1.1 or later Same toolkit covers M241 and M251
Libraries CAA File (included in Machine Expert install) Provides CAA_File, CAA_DirList, CAA_FileCopy
OSCAT OSCAT BASIC or NETWORK library, build 311 or later Optional: provides pre-built HTTP parsing helpers
Storage Schneider TMASD1 SD card, FAT32 formatted Mapped as /sd0 in runtime
Browser Chrome / Edge / Firefox, current LTS Must support fetch and CORS
Chart library dygraphs 2.x (free) or HighCharts (commercial) dygraphs preferred for CSV-native loading

4. Step 1 - Allocate Storage and Naming Conventions

Before any FB code is written, define the file layout so the JSON metadata and the front-end can agree on the file list.

  • Persistent CSV archive root: /sd0/logs/
  • Working copy root: /usr/visu/temp/
  • CSV naming pattern: YYYYMMDD_HH.csv, e.g. 20240318_14.csv
  • Metadata file (regenerated on each file copy): /usr/visu/temp/graphconfig.json
  • Index page: /usr/visu/index.htm (custom HTML, not WebVisu)
  • Default WebVisu page: /usr/visu/webvisu.htm (kept separate from index.htm)

The separation between index.htm and webvisu.htm is mandatory: EcoStruxure Machine Expert overwrites any file matching the configured WebVisu output name on every download.

5. Step 2 - HTTP Server on the Controller

Schneider's TCP server example (available in the Machine Expert sample gallery) provides the request/send primitives. Wrap them with a state machine that parses GET requests and routes them to local files.

5.1 Server State Machine

// Pseudo-ST for the request loop
CASE sState OF
    0: // WAIT_HEADER
        iRes := SysSockRecv(iListen, ADR(sBuf[0]), SIZEOF(sBuf), 0);
        IF iRes > 0 THEN
            sState := 10;
        END_IF
    10: // PARSE_REQUEST
        IF FIND(sBuf, 'GET /temp/') = 1 THEN
            sFile := MID(sBuf, FIND(sBuf,'/temp/'), FIND(sBuf,'HTTP') - FIND(sBuf,'/temp/') - 1);
            sState := 20;
        ELSIF FIND(sBuf, 'GET /graphconfig.json') = 1 THEN
            sState := 40;
        ELSE
            sState := 99; // 404
        END_IF
    20: // STREAM_FILE
        // Open /usr/visu/temp/<sFile> via CAA_File, send in 1 KB chunks
        // Include header: Access-Control-Allow-Origin: *
    40: // STREAM_JSON
        // Open /usr/visu/temp/graphconfig.json and stream
    99: // ERROR_404
        // Send "HTTP/1.1 404 Not Found\r\n\r\n"
END_CASE;

5.2 Mandatory HTTP Response Headers

HTTP/1.1 200 OK
Content-Type: text/csv; charset=utf-8
Access-Control-Allow-Origin: *
Cache-Control: no-store
Connection: close

Without the CORS header the browser will block the AJAX request initiated from the WebVisu page, which lives on the WebVisu port (default 8080 by default on M241/M251, but a different number on some firmware versions).

If you also need to support OPTIONS preflight requests, add a METHOD_NOT_ALLOWED branch that returns the same CORS header set. This is only required if you use custom request headers.

6. Step 3 - File Copy and JSON Generation with CAA File Library

The CAA File library ships a copy function block that handles buffered I/O across SD card and internal flash. Typical cycle for one CSV:

  1. Operator selects the file in the WebVisu using a combo box bound to wFileSel INT variable.
  2. Press of the WebVisu button sets xCopyRequest := TRUE.
  3. ST code copies the file, regenerates the JSON, then resets xCopyRequest.

6.1 CAA File Copy Skeleton

fbCopy(
    xExecute := xCopyRequest,
    sSrcPath := CONCAT('/sd0/logs/', sSelectedFile),
    sDstPath := CONCAT('/usr/visu/temp/', sSelectedFile),
    eMode := CAA_FILE_MODE_COPY,
    xDone => xCopyDone,
    xBusy => xCopyBusy,
    xError => xCopyError,
    eError => eCopyErr,
    wStatus => wCopyStatus);

6.2 Regenerating graphconfig.json

After each copy operation, the controller enumerates /usr/visu/temp/ using CAA_DirList and writes a small JSON file. The field-proven output looks like this:

{
  "timestamp": 1710800400,
  "files": [
    { "name": "20240318_14.csv", "size": 57344, "rows": 14336 },
    { "name": "20240318_15.csv", "size": 58000, "rows": 14500 }
  ],
  "series": [
    "PV_Voltage", "PV_Current", "Grid_Freq", "Inv_Temp"
  ]
}

The integer timestamp is critical: the JavaScript poller compares the latest value to the cached value and only re-parses the file list when it changes.

7. Step 4 - Custom HTML and dygraphs Integration

The custom index.htm is loaded directly in the browser via http://<plc-ip>/usr/index.htm or, in tighter integrations, via an iframe from a SCADA host. The JavaScript layer performs four steps every poll:

  1. Fetch graphconfig.json over AJAX.
  2. Compare the timestamp field. If unchanged, exit.
  3. Update the file selector dropdown with the new file list.
  4. Fetch the selected CSV and pass it to a new dygraphs DataSet.

7.1 Polling Skeleton

let lastTs = 0;
async function poll() {
  const r = await fetch('http://' + PLC + ':8080/graphconfig.json', {cache:'no-store'});
  const cfg = await r.json();
  if (cfg.timestamp === lastTs) return;
  lastTs = cfg.timestamp;
  rebuildFileSelector(cfg.files);
  if (currentFile) loadCsv(currentFile);
}
setInterval(poll, 1000);

async function loadCsv(name) {
  const text = await fetch('http://' + PLC + ':8080/temp/' + name, {cache:'no-store'}).then(r=>r.text());
  const rows = text.trim().split(/\r?\n/).map(l => l.split(','));
  // Assume first row is header; first column is timestamp
  const data = rows.slice(1).map(r => [new Date(parseInt(r[0])*1000), ...r.slice(1).map(Number)]);
  new Dygraph(document.getElementById('chart'), data, {
    labels: rows[0],
    legend: 'always',
    showRoller: true,
    ylabel: 'Value',
    xlabel: 'Time'
  });
}

7.2 Recommended CSV Layout

Column Format Example
0 UNIX timestamp (seconds, integer) 1710800400
1..N Floating point, decimal point 48.32
N+1 Optional status byte 0

Keep the CSV strictly RFC 4180 compliant; dygraphs will not auto-correct mixed decimal commas in the French locale.

8. Alternative Architecture: Java Applet + FTP

Where Java applets are still accepted inside the facility browser, an alternative path avoids the HTTP server entirely:

  1. Embed a Java applet in a WebVisu HTML page.
  2. Applet opens an FTP session to the controller's internal FTP server (default user USER, password configured in the device).
  3. Applet downloads the chosen CSV directly to a signed file URL.
  4. Applet hands the parsed data to a JavaScript chart via document.appletCallback(...).

This works but is fragile: modern Chrome and Edge have removed the NPAPI plugin stack, so applets are effectively deprecated. Treat it as a transitional workaround only.

9. Alternative: In-Memory FIFO Buffer with SoMachine

For installations with very small log volumes that fit inside 128 MB, the cleanest path is to keep the data in a REAL array and rotate it on each new sample. SoMachine/EcoStruxure Machine Expert BASIC library exposes:

FC_RolArrReal(
    i_iShiftPosNumber := 1,
    i_prStartAddr     := ADR(rTrendBuffer[0]),
    i_bySize          := SIZEOF(rTrendBuffer));
rTrendBuffer[N-1] := rNewSample;

The trend can be drawn directly inside WebVisu using the built-in trend element. This avoids the HTTP server, the SD card, and the browser entirely. It is the right choice for shifts under 24 hours with sample rates above 1 Hz.

10. Verification Checklist

Step Expected Result Validation Method
Start WebVisu in browser Default webvisu.htm loads with file selector Visual inspection
Load custom index.htm Page shows file selector and empty chart Browser dev tools network tab
Press Get button in WebVisu CSV appears in /usr/visu/temp/ within ~1 s FTP listing
graphconfig.json polled timestamp increments after each copy Wireshark on port 8080
CSV plotted dygraphs renders series without console errors Browser console + chart visible
CORS check No "blocked by CORS" errors Browser console
CPU impact Controller scan time increase < 2 ms Task monitoring

11. Troubleshooting Matrix

Symptom Likely Cause Corrective Action
Browser shows blank page after upgrade EcoStruxure Machine Expert overwrote index.htm Rename the custom page (for example to launch.htm) and re-deploy
AJAX fails with "blocked by CORS" Missing Access-Control-Allow-Origin header Verify HTTP server emits header on every response, including 404
File copy returns error 1 SD card not present or wrong path Verify /sd0 exists; FAT32 only; replace with TMASD1 part
CSV downloads but chart empty Locale mismatch; decimal commas Re-generate CSVs with '.' decimal point, or set dygraphs delimiter option
Controller watchdog resets under load HTTP server blocking main task Run HTTP server in a dedicated background task with lower priority
Timestamp never updates in JSON CAA_File Copy fails silently Check eCopyErr and wCopyStatus diagnostics; ensure destination directory exists
dygraphs shows "Wrong number of columns" Mixed line endings in CSV Normalize to \n before writing; check the source logger
Java applet no longer runs Browser NPAPI removed Switch to the HTTP server + AJAX architecture

12. Performance and Sizing Notes

Use the following formulas to size polling cadence against the scan time budget:

  • JSON poll cost per browser: roughly 1 KB * 1 Hz = 8 kbit/s per client.
  • CSV stream cost per browser: file_size_KB * 8 * refresh_rate_Hz / 1024 = Mbit/s.
  • Controller-side copy throughput on M251: approximately 56 KB / 250 ms with default SD card class.
  • Recommended maximum concurrent browsers: 5 per controller before scan time impact exceeds 5 ms.
If multiple engineers are viewing the trend simultaneously, consider adding a thin-node cache (nginx on a server PC) that proxies the controller's HTTP responses. This isolates the controller from read storms.

13. Frequently Asked Questions

Can the M241/M251 WebVisu trend a CSV file directly without a custom HTTP server?

No. The embedded WebVisu trend element only reads from internal real arrays. CSV data on the SD card must be copied to /usr/visu/temp/ and exposed through an HTTP or FTP service before the browser can render it.

What is the practical maximum CSV size for a single chart?

dygraphs performs well up to roughly 200,000 rows in current desktop browsers. Beyond that, decimate the log to one-minute averages before plotting, or switch to a uPlot/Plotly renderer that supports canvas virtualization.

Does the CAA File library support append-mode writes for live logging?

Yes. CAA_File exposes CAA_FILE_MODE_APPEND, which keeps the file handle open and lets the application append rows on each cycle. Use a separate task with low priority to avoid impacting the main scan.

Why is the Access-Control-Allow-Origin header necessary?

The custom index.htm is served from the WebVisu port while the CSV is served from the custom HTTP port. Browsers treat them as different origins and block the AJAX request unless the server explicitly opts in via the CORS header.

Is this approach supported by Schneider Electric?

Schneider does not endorse or document the custom HTTP server architecture; it is an integrator implementation. For production deployments in regulated industries, validate with Schneider support and consider Modicon M340/M580 controllers with native OPC UA trending as an alternative.

Back to blog