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:
- WebVisu pages cannot natively load files outside
/usr/visu. - 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: *. - 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
NETlibrary.
2. System Architecture Overview
The solution chains four building blocks:
- TCP/HTTP server FB on the PLC, listening on a port distinct from the WebVisu HTTP port (for example 8080).
-
CAA File library FB instances that copy CSV files from
/sd0/to/usr/visu/temp/on demand. -
JSON metadata generator that produces
graphconfig.jsondescribing available files and their timestamps. -
Custom HTML page served from
/usr/visuthat iframes the WebVisu and uses AJAX to pollgraphconfig.jsonplus 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).
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:
- Operator selects the file in the WebVisu using a combo box bound to
wFileSelINT variable. - Press of the WebVisu button sets
xCopyRequest := TRUE. - 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:
- Fetch
graphconfig.jsonover AJAX. - Compare the
timestampfield. If unchanged, exit. - Update the file selector dropdown with the new file list.
- Fetch the selected CSV and pass it to a new
dygraphsDataSet.
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:
- Embed a Java applet in a WebVisu HTML page.
- Applet opens an FTP session to the controller's internal FTP server (default user
USER, password configured in the device). - Applet downloads the chosen CSV directly to a signed file URL.
- 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/sper 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 mswith default SD card class. - Recommended maximum concurrent browsers: 5 per controller before scan time impact exceeds 5 ms.
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.