A local AI powered web app that generates and optimizes Rockwell ControlLogix User-Defined Data Types (UDTs) for export to Studio 5000 as .L5X files.
Three input modes:
- Natural language — describe a UDT in plain English, a local Ollama model extracts the field list, the app validates and emits an L5X.
- L5X attachment — drop in an existing UDT or full-controller export, get back an optimized version with members reordered for minimum memory footprint and BOOLs bit-packed.
- CSV / Excel batch — one tab or row-group per UDT, output is a zip of L5X files.
This is just a experimental extension of L5X UDT Optimizer. It leverages a local LLM to generate Rockwell Studio 5000 UDTs directly from NLP. This repo has achieved its intended purposes.
Requires Python 3.10+ and (for mode #1) Ollama running locally with at least one model pulled.
pip install -r requirements.txt
ollama pull phi4 # or any model you preferpython3 app.pyOpens on http://127.0.0.1:5003. The app hard-binds to localhost and refuses to start on a non-loopback interface — there's no auth and it reads/writes engineering source files. If you really need a different host, set LLM4UDT_HOST=127.0.0.1 (or run it behind a reverse proxy with auth in front).
Optional environment variables:
| Variable | Default | Purpose |
|---|---|---|
LLM4UDT_HOST |
127.0.0.1 |
Bind address. Only loopback values accepted. |
FLASK_DEBUG |
0 |
Set to 1 to enable Werkzeug debugger (RCE). |
Open Settings (⚙️), pick an Ollama model, then in the chat box:
Create a UDT named MotorStatus with: run_status, BOOL, indicates if motor is running; fault_code, DINT; speed_setpoint, REAL; alarm_bits, BOOL[64]; phase_currents, REAL[3]
Field syntax is strict — fields separated by ;, values within a field by , (name, type, optional description). Supported types: BOOL, SINT, INT, DINT, REAL, STRING, TIMER, COUNTER plus arrays TYPE[N]. BOOL arrays must be multiples of 32 up to 1024.
Drag in a .L5X file (single UDT or full controller export). The app reports issues found (sort order, unpacked BOOLs, invalid BOOL array sizes) and offers an Optimize button. Output is a download.
For a full-program L5X, only the <DataTypes> section is rewritten — programs, tags, modules, AOIs, and tasks pass through untouched.
CSV columns (case-insensitive): UDT_Name, Tag_Name, Data_Type, Description. Rows grouped by UDT_Name. Excel: one sheet per UDT, sheet name = UDT name. Output: a zip of L5X files.
Members are sorted into groups (then natural-key by name within each group):
| Group | Contents |
|---|---|
| 0 | BOOL scalars (packed into SINT bit-fields) |
| 1–3 | SINT, INT, DINT scalars |
| 4 | All arrays (sub-sorted BOOL → SINT → INT → DINT → REAL → LREAL → LINT → STRING → other) |
| 5–7 | REAL, LREAL, LINT scalars |
| 8 | STRING scalars (including Family="StringFamily" user types like STRING_40) |
| 9 | TIMER, COUNTER, CONTROL, MESSAGE, AOI instances |
| 10 | Nested UDT scalars |
| 11 | Unresolved / unknown types |
Byte sizes follow Rockwell layout: natural alignment per type, BOOL[N] stored as ceil(N/32) × 4 bytes, consecutive BIT members packed into a single SINT block, final struct rounded to maximum member alignment.
Unresolved types are never silently dropped — they're carried through verbatim with a warning, so a UDT referencing a type defined elsewhere in the program won't lose members.
python3 test_core.py29 regression tests, no external dependencies (Ollama is stubbed). Exits 0 on success.
Test areas:
- Engine-consistent sort in the program analyser (no stale local sort tables).
- BOOL[N] rounds up, never down —
BOOL[33]becomesBOOL[64], notBOOL[32].BOOL[>1024]is a blocking error, not silent truncation. - LLM JSON fence parsing tolerant of CRLF, missing language tag, uppercase tags, missing trailing newlines.
- Stored-file cache is bounded (capped + TTL-evicted) and thread-safe.
- Localhost-only enforcement at startup.
- Upload directory purged at startup and per-request.
- String-family UDTs (
STRING_40etc.) sort with native STRING, not as arbitrary nested UDTs.
| File | Purpose |
|---|---|
app.py |
Flask routes and request handling |
model_UDTGen.py |
Ollama → JSON tag extraction |
Validator_UDT.py |
Name sanitization, type validation, BOOL array sizing |
L5XGen_UDT.py |
L5X XML generation, member sort key, type sizes |
L5XOpt_UDT.py |
UDT optimization, alignment math, full-program rewrite |
Attach_L5Xanalyzer.py |
Detects L5X target type (DataType / Controller) |
Attach_UDTVerify.py |
Issue analysis for a single attached UDT |
Attach_ProcessL5XProgram.py |
Issue analysis + rewrite orchestration for full programs |
Attach_ProcessCSV.py |
CSV batch → zip of L5X |
Attach_ProcessExcel.py |
Excel batch → zip of L5X |
Attach_UDTBatch.py |
Shared batch zip builder |
test_core.py |
Regression test suite |
- Single-process only. State (
stored_files,known_udts,active_model) lives in module globals. Multi-worker deployment would need a session store. - AOI size estimation only considers Parameters, not LocalTags. For AOIs with significant local storage the estimate will be low.
- LLM output is best-effort. If the model returns malformed JSON the request fails cleanly with a hint to rephrase — there's no retry loop.