plc_pou
Create, read, write, and manage PLC objects offline in TwinCAT XAE: programs, function blocks, GVLs, and more with batch operations and surgical text editing.
Instructions
PLC object authoring + code edit on the open solution (OFFLINE engineering only — edits land in-memory and reach a runtime only via a later guarded plc_download + twincat_restart_runtime). Tree paths use ^ separators; safety (TISC-rooted) paths are rejected by policy. CREATE — create / create_batch (parent, name, subType, language?, returnType?, extends?, implements?, declText?, before?): CreateChild sub-types 602 Program, 603 Function (returnType required), 604 FunctionBlock, 605 Enum, 606 Struct, 607 Union, 608 Action, 609 Method, 611 Property (returnType required), 615 GVL, 616 Transition, 618 Interface, 619 Visualization, 623 Alias, 629 ParameterList, 631 UML. language IECLANGUAGETYPES 0 NONE/1 ST/2 IL/3 SFC/4 FBD/5 CFC/6 LD (default 1). extends/implements for FB 604 / Program 602 derivation (618 uses extends as its base); declText seeds DUT/GVL decl. For code POUs prefer set_decl after create. FOLDERS — create_folder (parent, name, before?) creates a PLC folder (sub-type 601) under parent (a PLC subtree node, POUs/DUTs/GVLs container, or another folder); returns the same shape as create. create_folder_batch (creates:[{parent,name,before?}], save?) loops continue-on-error, returns {count,succeeded,failed,results} KEEPING success rows (each carries the created child identity) — list a parent-folder entry before its child (array order). create / create_batch already author INTO a folder when parent is the folder's path — no separate action needed. TEMPLATE — import_template (parent, paths[]) imports POU template file(s) (CreateChild sub-type 58). READ (cheap-first: outline for structure; get_decl/get_impl with grep{} or range{} to slice; full text only when the whole body is needed — a large full read returns a hint nudging the next call to slice) — get_decl / get_impl / get_document / get_graphical (path). get_decl/get_impl take an optional range {start,end} (1-based inclusive line slice, clamped) OR grep {pattern, context?} (regex over lines + context each side); mutually exclusive; default full text. Both report lineCount; get_impl also returns language (textual 1 ST/2 IL; graphical 3 SFC/4 FBD/5 CFC/6 LD -> lineCount:0 + {graphical:true, hint}). get_graphical (path) READ-ONLY inspects a graphical (LD/FBD/SFC/CFC) body: returns {language,languageName,itemType,source,readOnly,xml} where xml is the object's network XML (NWL 'BoxTree' for LD/FBD/IL, or the SFC/CFC archive), pulled live from the POU document (for an Action/Method/Transition it reads the PARENT POU's document, since get_document/GetDocumentXml only work on a top-level POU). Diagnostic only — graphical bodies are NOT text-editable; change them in the XAE GUI. Refuses textual languages (use get_impl). outline (path) returns structure WITHOUT full text: header + varBlocks + child code items. WRITE — set_decl / set_decl_batch (path, declText); set_impl / set_impl_batch (path, exactly one of implText|implXml — implXml is TwinCAT object XML, round-trip only, for graphical languages); set_document (path, documentXml). SURGICAL TEXT EDIT (read-modify-write, returns ONLY the changed region +/-2 ctx; target decl|impl, CRLF/LF preserved; refuses graphical impl): replace (find literal substring, replaceWith, expectCount? default 1 — fails without writing on 0 or count mismatch); replace_lines (start, end, text — 1-based inclusive span, OOB throws); insert (exactly one of at|after|before, text); insert_in_var_block (block e.g. VAR_INPUT, text, occurrence? — inserts before that block's END_VAR); append (text — default target impl). All surgical writes accept validate:true to run CheckAllObjects after (default off). DISCOVER (cheap-first: find for path-by-name; search only for content patterns) — tree (plcPath?, path? subtree root, depth?, typeFilter?) does a read-only recursive Child() walk of the IEC project and returns {plcPath,projectPath,rootPath,count,tree:[{path,name,type,itemType,subType?,childCount,children?,truncated?}]} (type is a normalized label: Program/FB/Function/FunctionBlock/Struct/Enum/Union/Alias/GVL/Interface/Method/Property/Action/Transition/Visualization/ParameterList/UML/Folder/Project/Task/Unknown; depth 1 = direct children only; typeFilter is a comma list of type labels to KEEP, ancestors retained as scaffolding). find (plcPath?, path?, name? substring or /regex/, typeFilter?; at least one of name/typeFilter) returns a FLAT {plcPath,projectPath,count,matches:[{path,name,type,itemType,subType?,childCount}]} so a caller can resolve a ^ path from a name without the whole nested blob. GREP — search (pattern [regex/.NET or substring], ignoreCase?, declOnly?|implOnly? [mutually exclusive], plcPath?, path? subtree root, maxResults? default 50/max 5000 — raise for exhaustive scans; truncated:true signals the cap was hit) is a project-wide find-in-code: walks every code object under the IEC project, greps DeclarationText + (ST-only) ImplementationText line-by-line, returns {pattern,plcPath,scanned,searched,count,truncated,matches:[{path,section:'decl'|'impl',line,text}]}; graphical bodies are scanned-but-not-searched. Read-only/offline. Decl/impl text is CACHED (warm repeat sub-100ms vs ~16s cold); the cache self-invalidates on edits through this tool, dirty-checks open IDE editors, and is backstopped by a file-save watcher — pass refresh:true to force a full live re-pull. DELETE — delete (path OR parent+name) GUARDED offline delete of one PLC object via parent.DeleteChild; dryRun:true previews {wouldDelete,target}, confirm="ALLOW_TWINCAT_DELETE" to actually delete; verifies the child exists first, refuses TISC. LIFECYCLE (OFFLINE, unguarded, refuses TISC) — rename (path, newName = bare name) renames in place, returns {path,newName,newPath}. move (path, newParent, before?) reparents one object preserving decl/impl/document/sub-objects via export-import-delete in ONE attach (no native reparent exists); refuses no-op/into-self/into-own-descendant moves; returns {path,newParent,newPath,name,via}. BUILD-CHECK — check_objects (plcPath?, default first PLC under TIPC) runs CheckAllObjects on the nested IEC project (no download). Mutating batch verbs (create_batch, set_decl_batch, set_impl_batch) accept save:true to save once after the batch.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| at | No | insert before this 1-based line (lineCount+1 appends) | |
| end | No | replace_lines: 1-based inclusive last line | |
| find | No | replace: exact literal substring (NOT regex) | |
| grep | No | get_decl/get_impl: regex over lines + context each side (default 2); mutually exclusive with range | |
| name | No | ||
| path | No | ||
| save | No | ||
| text | No | replacement / insert / append text | |
| after | No | insert after this 1-based line | |
| block | No | insert_in_var_block: VAR-block keyword e.g. VAR_INPUT | |
| depth | No | tree: max recursion depth (1 = direct children only); default unlimited | |
| items | No | ||
| paths | No | ||
| range | No | get_decl/get_impl: 1-based inclusive line slice; mutually exclusive with grep | |
| start | No | replace_lines: 1-based inclusive first line | |
| action | Yes | ||
| before | No | create: sibling name to insert before (string). insert: 1-based line to insert before (int, alias of at) | |
| dryRun | No | delete: preview the target without deleting | |
| parent | No | ||
| target | No | surgical edit target; default decl (append defaults impl) | |
| confirm | No | delete: must equal ALLOW_TWINCAT_DELETE to actually delete | |
| creates | No | ||
| details | No | set_decl_batch/set_impl_batch: include ok:true rows; default failures-only ({count,succeeded,failed} always reported). create_batch/create_folder_batch always keep success rows (they carry child identity), so details is a no-op there. | |
| extends | No | ||
| implXml | No | ||
| newName | No | rename: new bare object name (not a path) | |
| pattern | No | search: regex (.NET syntax) or plain substring, matched per-line against each object's decl/impl text | |
| plcPath | No | ||
| refresh | No | search: force a full live re-pull, bypassing the decl/impl text cache for the searched scope (default false). Open editors are always dirty-checked automatically; use this only as an escape hatch after structural ops or for paranoia. | |
| subType | No | ||
| declOnly | No | search: search only DeclarationText; mutually exclusive with implOnly | |
| declText | No | ||
| implOnly | No | search: search only ImplementationText (ST-only); mutually exclusive with declOnly | |
| implText | No | ||
| language | No | ||
| validate | No | surgical writes: run CheckAllObjects after the edit (default off) | |
| newParent | No | move: ^-separated destination parent tree path (TISC refused) | |
| ignoreCase | No | search: case-insensitive match (default false) | |
| implements | No | ||
| maxResults | No | search: cap on returned match rows (default 50, max 5000; raise for exhaustive scans); stops the walk and sets truncated:true when hit | |
| occurrence | No | insert_in_var_block: which matching block (1-based, default 1) | |
| returnType | No | ||
| typeFilter | No | tree/find: comma list of normalized type labels to keep/match (case-insensitive), e.g. 'FB,Method,Struct' | |
| documentXml | No | ||
| expectCount | No | replace: required occurrence count (default 1) | |
| replaceWith | No |