Guidelines
akash-network/node
Behavioral guidelines to reduce common LLM coding mistakes. An agent skill from akash-network/node.
Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loop --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/codesys-loop .claude/skills/codesys-loop && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .claude/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loopType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loop --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/codesys-loop .agents/skills/codesys-loop && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .agents/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loop --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/codesys-loop .cursor/skills/codesys-loop && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .cursor/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git --path .claude/skills/codesys-loop--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loop --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/codesys-loop .gemini/skills/codesys-loop && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .gemini/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loopInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/codesys-loop .github/skills/codesys-loop && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .github/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install MichielVanwelsenaere/HomeAutomation.CoDeSys3 codesys-loop --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/codesys-loop .opencode/skills/codesys-loop && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "codesys-loop" agent skill from https://github.com/MichielVanwelsenaere/HomeAutomation.CoDeSys3/tree/master/.claude/skills/codesys-loop into .opencode/skills/codesys-loop/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "codesys-loop", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
codesys-loopRead, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine.
Codesys Loop is an agent skill from MichielVanwelsenaere/HomeAutomation.CoDeSys3. Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine. Use when adding or changing a function block, refactoring ST code, checking whether a change compiles, re-exporting src/Exports/PLCopen.xml, inspecting the project structure inside the binary .project file, or setting up the toolchain needed for any of that.
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Refactoring. The repository describes itself as: Home Automation system build in CoDeSys 3 with MQTT communication to any third party Home Automation software. The licence is MIT.
6 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 924c3da. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
wingetgitFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
3s-software.comw3.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Codesys Loop loads about 11k tokens when it runs. Until then it costs about 103 tokens; SKILL.md has 6,025 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from MichielVanwelsenaere/HomeAutomation.CoDeSys3 at commit 924c3da, republished under its MIT licence (© MichielVanwelsenaere). 6,025 words, ~11,438 tokens.
.claude/skills/codesys-loop/SKILL.md (or your agent's skills folder).src/HomeAutomation.project is a binary. It cannot be read, diffed or edited
as text. The way in and out is PLCopen XML, which CODESYS both exports and
imports — and because CODESYS ships a ScriptEngine and runs with --noUI, the
real compiler can judge the result. Never reason about ST correctness from the
XML alone when you can compile it instead.
Two entry points, both committed at a normal path so CI and contributors can use them too, not only agents:
tools/ai/codesys.ps1 — the wrapper you run.tools/ai/codesys_task.py — runs inside CODESYS (IronPython 2.7). Don't run
it directly../tools/ai/codesys.ps1 doctorFast, does not launch CODESYS. Required:
| Item | Notes |
|---|---|
| CODESYS 3.5 SP21 (3.5.21.30) | Includes the ScriptEngine and the IronPython stdlib. No extra license needed for --noUI scripting. |
ScriptEngine.dll | Under CODESYS\Common\. The entire harness depends on it. |
| CODESYS Control for PFC200 SL | The project's device. Without it the device will not resolve and the build fails. |
| WAGO Device Support Package 2.0.8.9 | Supplies WagoAppDALI, which FB_OUTPUT_DIMMER_DALI_MQTT needs. Without it the build fails on that block, not just on DALI. It is not vendored — WAGO's licence forbids redistribution — so install it per machine: docs/WagoPfcPrep.md#installing-the-wago-libraries-dali. WagoAppDALI is qualified-only, unlike in e!COCKPIT: its types are WagoAppDALI.typBallast, WagoAppDALI.FbDaliSendDimValue and so on, so ST lifted out of an e!COCKPIT project does not compile until it is qualified. |
| Windows PowerShell 5.1 | The scripts avoid PowerShell 7-only syntax, so either works. |
| mosquitto clients | Optional, and what every runtime check here runs on. winget install --id EclipseFoundation.Mosquitto --scope machine. The installer registers a broker service that is not needed here (leave it stopped) and does not add itself to PATH; the tooling also looks in C:\Program Files\mosquitto. |
Override discovery with -Exe / -CodesysProfile, or set $env:CODESYS_EXE.
Python is not needed by this harness — only by the update-fb-docs skill.
doctor reports it as a warning, not a failure.
gen_fb_docs.py needs 3.7+ (it uses f-strings and
from __future__ import annotations). Install a current release:
winget install --id Python.Python.3.12 --scope machineThen open a new shell and verify:
py --versionTwo Windows gotchas worth knowing before you debug a "Python was not found" message:
winget installs the python.org build, which provides python.exe and the
py launcher but no python3.exe. On this machine, invoke the docs
generator as py .claude/skills/update-fb-docs/scripts/gen_fb_docs.py.python/python3 that opens the
Microsoft Store instead of running anything. If a bare python still opens the
Store after installing, turn the aliases off under
Settings → Apps → Advanced app settings → App execution aliases. doctor
already ignores anything resolving inside WindowsApps for this reason.Each task boots CODESYS, so budget 40–90 seconds. A --noUI process has no
usable stdout: results go to .ai/reports/<task>.json, with a progress log at
<task>.json.log that survives a crash. .ai/ is gitignored.
.ai/work is wiped at the start of every verify and simulate — it is the
sandbox. Never keep anything there. Edit fragments belong in .ai/edits/.
| Command | Effect |
|---|---|
./tools/ai/codesys.ps1 doctor | Toolchain check. No CODESYS launch. |
./tools/ai/codesys.ps1 tree | Dump the project object tree. |
./tools/ai/codesys.ps1 device | Report the device tree, the configured gateway, address and simulation flag. Connects to nothing. |
./tools/ai/codesys.ps1 device -AddModule <ModuleId> -Under <node> -Force | Plug a module into the device tree. Builds first and refuses to save a project that does not build. |
./tools/ai/codesys.ps1 device -AddDevice <type:id:version> -NodeName <name> [-Under <node>] -Force | Add a device — a module, or a whole second controller at the project root. |
./tools/ai/codesys.ps1 device -RemoveNode <name> -Force | Unplug a device or module. |
./tools/ai/codesys.ps1 device -MapIo <spec.json> -Force | Name a terminal's I/O channels, which is what makes a freshly added module readable from IEC code at all. { "map_io": [ { "node", "channel", "variable" } ] }; each mapping is read back and a mismatch is an error. |
./tools/ai/codesys.ps1 device -RenameNode <name> -NodeName <new> -Force | Rename a device or module. Re-record the baseline afterwards: a device's name is in every message's object path. |
./tools/ai/codesys.ps1 scaffold -Scaffold <spec.json> -Force | Create or update GVLs, programs and tasks inside an application, including a task's kind, interval and priority. |
./tools/ai/codesys.ps1 scan | List PLCs answering on each gateway. Read-only, needs no project. |
./tools/ai/codesys.ps1 download -Force | Full download of the real project to the real PLC, then start it. |
./tools/ai/codesys.ps1 export | Rewrite src/Exports/PLCopen.xml from the project. |
./tools/ai/codesys.ps1 verify -Baseline | Build the untouched project, store its messages as the baseline. |
./tools/ai/codesys.ps1 verify | Import .ai/candidates/*.xml into a sandbox copy, build, report. |
./tools/ai/codesys.ps1 simulate | Download to a simulated PLC and run a test spec. Currently blocked — see below. |
./tools/ai/codesys.ps1 apply -Force | Import candidates into the real project and save. |
./tools/ai/codesys.ps1 rename -Map <map.json> -DryRun | Report what a rename would touch. Writes nothing, builds nothing. |
./tools/ai/codesys.ps1 rename -Map <map.json> -Force | Rename objects and identifiers, rewriting every reference. Refuses to save unless it builds. |
./tools/ai/codesys.ps1 probe | Dump real .NET signatures of the scripting API. |
./tools/ai/codesys.ps1 info | Read-only: IDE version, libraries, devices, and a hash of every object's code. |
./tools/ai/codesys.ps1 compare -Project A -Against B | CODESYS's own object-level diff between two projects. Broken on 3.5.21.30: compare_to rejects the flags it is given with "Cannot convert numeric value 5 to ComparisonFlags. The value must be zero." Diff two exports instead. |
./tools/ai/codesys.ps1 libs | Read-only: every library reference against every version installed on this machine. |
./tools/ai/codesys.ps1 libs -RemoveLib '#Name' | Drop a library reference. Builds first, refuses to save if it does not build. |
-Project <path> points any task at a different .project file — that is how
sync-implementation-project drives a building's installation project. Reports
are then stem-qualified (baseline.SiteA.json), so a foreign build
cannot overwrite this project's baseline. download refuses -Project.
Four companions to it:
| Flag | Why |
|---|---|
-Candidates <dir> | A separate candidate set, so two pieces of work cannot import into each other's project. |
-Only <names> | Export just some objects. Errors by name if one is not found rather than exporting less than asked. |
-ImportFolders | Honour the folder structure in a candidate exported from another project. Without it the block lands at the project root, is never compiled, and the build stays green while the import achieved nothing. |
-ImportConflict replace | Replace an object that already exists. Without a policy the importer has no say and files a second object of the same name, leaving the original compiled. |
The last two fail the same way — silently, with a green build — and a candidate lifted out of another project needs both.
Confirm the export is current before trusting it:
git log -1 --format=%cd -- src/Exports/PLCopen.xml src/HomeAutomation.projectIf the project is newer, run export first. Regenerating docs or reasoning
about pins from a stale export bakes in wrong interfaces.
Record a baseline once per session: verify -Baseline. The project builds
with 9 pre-existing warnings — three OSCAT CONSTANTS_SETUP string-length
warnings, three IP_CONTROL2 sign conversions, GVL_PERSISTENT, one genuine
sign conversion in PRG_DMX_SEND line 70, and one for
PRG_DALI_VERIFY.Dimmer.DimValue having no persistent list on
Wago_PFC200_G2_Virtual. That last one is load-bearing: it is only
reported because the DALI block really is compiled there, so if it ever
disappears, the verification application has stopped verifying. The
application is never downloaded, so nothing needs retaining. With a baseline
recorded, verify prints a NEW vs baseline section so your own changes
stand out. Re-record after a library or CODESYS version change.
Read the current code from src/Exports/PLCopen.xml: declarations, ST
bodies, methods and the task configuration are all there.
Write the change as a single-block PLCopen file in .ai/candidates/.
Start from tools/ai/templates/FB_TEMPLATE.xml, which documents the dialect.
For anything non-trivial, copy the shape of a comparable existing block out of
PLCopen.xml rather than inventing structure.
Verify — ./tools/ai/codesys.ps1 verify. This copies the project to
.ai/work, imports the candidates, builds, and reports every message with
object, line and column. Iterate here; the real project is untouched. Read the
harness section of the report, not just the result line (see below).
Apply only once verify is clean: apply -Force. It refuses to save if the
build fails. Then re-export so the XML matches the binary again, and update
the docs with the update-fb-docs skill.
Steps 6 onwards change tracked files, including a binary. Ask before running them unless the user has already said to land the change.
A candidate XML import REPLACES the whole object, so it is right for a new
block and wrong for changing an existing one — a partial file silently drops
every method it omits, and appending to an existing POU's plaintext declaration is
ignored outright. For existing code use .ai/edits/edits.json, which drives the
ScriptEngine's textual API and is applied by both verify and apply:
{ "edits": [
{ "pou": "FB_MQTT_BASE", "decl_append_file": "base.decl",
"skip_if_contains": "FriendlyName" },
{ "pou": "FB_OUTPUT_BINARY_MQTT", "body_prepend_file": "prologue.st",
"skip_if_contains": "self-wiring prologue" },
{ "pou": "PRG_MAIN", "decl_replace_file": "main.decl" },
{ "pou": "PRG_MAIN", "member": "MAIN_INIT", "body_replace_file": "main_init.st" }
] }| Key | Effect |
|---|---|
pou | POU, program or GVL name. Must resolve to exactly one object that owns text. |
path | Substring of the object path, matched case-insensitively, to disambiguate pou. |
member | Target a method or action instead of the POU itself. |
decl_append / decl_replace | Declaration, inline text or *_file. |
body_prepend / body_append / body_replace | Implementation, same. |
skip_if_contains | Idempotence sentinel. If the target text already contains it, that append/prepend is skipped. |
Notes that cost real time to rediscover:
skip_if_contains on an append or prepend. An edit spec gets
re-run constantly during a refactor, and without the sentinel a second run
duplicates the text. *_replace needs no guard.*_file paths resolve relative to the spec file, then to the repo root. Keep
fragments in .ai/edits/ — never .ai/work, which verify deletes.PRG_MAIN, both real, both owning
text, and every edit addressed by name alone is then refused as ambiguous.
"path": "Wago_G1_Annex/" picks one. The refusal is the useful behaviour —
the alternative is a coin flip over which building gets rewritten — so do not
work around it by renaming a program. delete_pou has no such qualifier yet;
it will report the same ambiguity and stop.insert() takes the offset first, the reverse of the shipped stub. Same trap
as export_xml.create_method works on an INTERFACE too, which is how a method reaches
I_RS485_DEVICE or I_RS485_TRANSPORT without hand-authoring interface XML: the
interface object owns text, so create_method plus decl_replace is the whole
job. Every implementer then needs the same method or the build fails by name —
which is the useful failure, and how the six RS485 device blocks were kept in
step when GetCommissioning was added./// doc comment written by decl_replace does not reach the export.
CODESYS only materialises <documentation> for a comment its own editor parsed,
so a method created from a script exports with its plaintext declaration intact
and no structured documentation — and update-fb-docs therefore shows it as
_TODO: describe this._ however carefully the fragment was commented. Write the
description in the doc page, or in the generator's GLOSSARY when the method
means the same thing on every block.-Edits none skips the spec, which is how you build the committed project
standalone as a control. A verify -Baseline never applies edits..ai/edits/gen.ps1 in history) beats
hand-writing 14 near-identical prologues.rename, not editsRenaming is its own task because node.rename() renames an object and updates
nothing that refers to it — CODESYS's IDE refactoring is not exposed to the
ScriptEngine. The references are the harness's job:
./tools/ai/codesys.ps1 rename -Map tools/ai/rename/179-objects.json -DryRun
./tools/ai/codesys.ps1 rename -Map tools/ai/rename/179-objects.json -ForceThe map holds objects (a type, block, program or GVL — the name is
project-unique, so every occurrence of the token is that object) and
identifiers (variables inside one declaring object). An identifier group's
mode decides how far it reaches:
| mode | rewrites | right for |
|---|---|---|
local | the declaring object and its methods and actions | a program's own instances, a block's internals |
qualified | also Owner.name project-wide | GVL members, enumeration values |
loose | also .name and name := project-wide | a function block's pins, whose qualifier is an instance name nothing can enumerate |
Five things learned building it, all of which cost a run:
Identifier 'PLC_PRG_MAIN' not defined <.../Task Configuration/MainTask>. The
task now renames the call twin too, and reports it as (task call).MqttVariables, and FB_MQTT_BASE declares
STRING(GVL_MQTT.MQTT_TOPIC_LEN), so the name has to resolve in both.
Ambiguity is refused unless the map says {"new": ..., "all": true} — or
"path" to pick one.PRG_DALI_VERIFY declared an
instance called Dimmer while the enumeration Dimmer was becoming
E_DIMMER; a blind sweep renames the instance too, consistently, and the
build stays clean with a variable now called E_DIMMER. The task refuses an
object rename whose old name is also declared as a variable, and identifier
groups run first so one map can move the variable out of the way.-DryRun versus 473 actually rewritten.Afterwards: verify -Baseline (object paths moved, so the old baseline reads
every unchanged warning as NEW), export, then update-fb-docs. The docs
generator keys the GVL region on the list's name, so a GVL rename means editing
gen_fb_docs.py too.
Established by compile probe, not by reading documentation:
A hand-written candidate CAN file itself into a folder. The skill's own
advice used to be that a candidate with no folders lands at the project root,
which is true only because nobody had put folders in one. Add a
projectstructure <addData> block naming the target folder and the object,
with no ObjectId attributes at all, and the default import path honours
it - the report then reads added: BASIC/FB_YOUR_NAME rather than a bare
name. Worth doing: folder placement is cosmetic to the compiler, but there is
no scripted way to move an object afterwards, so the alternative is IDE
hand-work.
<addData>
<data name="http://www.3s-software.com/plcopenxml/projectstructure" handleUnknown="discard">
<ProjectStructure>
<Folder Name="BASIC">
<Object Name="FB_YOUR_NAME">
<Object Name="InitMqtt" />
</Object>
</Folder>
</ProjectStructure>
</data>
</addData>The methods are listed as nested <Object>s; the import adds each and reports
it. FB_INPUT_TEMPERATURE_RTD_MQTT landed in BASIC/ this way.
Plaintext declarations work for a NEW POU, but do not reliably override an
existing one. Authoring a brand-new block with a plaintext declaration and an
empty <interface /> works (proven). But taking an existing POU out of the
export, appending a VAR_INPUT block to its plaintext declaration and
re-importing it does not apply the new members: the block keeps exactly its
old declaration, the import still reports replaced 1, and the only symptom is
Identifier 'X' not defined in whatever referenced the new member. Emptying the
structured <interface> did not change this. Unresolved — the next attempt
should isolate it by (a) removing the <interface> element entirely rather than
emptying it, (b) trying a plain INT member to rule out the enum type, and
(c) comparing against editing the structured <interface> instead, which is
the safer default until this is understood. Prefer the ScriptEngine textual API
(textual_declaration.append) for editing an existing POU's declaration; that
path is already proven by the verify harness, which injects into a real program
this way on every run.
A <dataType> takes <baseType> then <addData>, and nothing else. There
is no <documentation> child on a DUT in tc6_0200; adding one fails the whole
file with
The element 'dataType' ... has invalid child element 'addData'which names the wrong element and reads like an ordering problem, so the obvious
fix — shuffling the children — does not help. Put a type's prose in the
per-value enumvaluedocumentation block instead. The import failure is easy
to miss: verify reports it in imports[].errors, then the build fails with
a hundred Identifier not defined errors for the type, and the real message is
the first one, not the hundred. E_RELAY_TYPE was authored this way — copy it.
For a NEW POU, put the plaintext declaration in an <addData> block on the
<pou>:
<data name="http://www.3s-software.com/plcopenxml/interfaceasplaintext" handleUnknown="implementation">
<InterfaceAsPlainText>
<xhtml xmlns="http://www.w3.org/1999/xhtml">FUNCTION_BLOCK FB_X EXTENDS FB_MQTT_BASE
VAR
x : INT;
END_VAR</xhtml>
</InterfaceAsPlainText>
</data>The importer prefers it over the structured <interface>, it is lossless, and
EXTENDS works. Get a template by running export -Plaintext, which writes
.ai/reports/PLCopen.plaintext.xml with every POU in this form.
A METHOD's interface must be structured XML. A nested
InterfaceAsPlainText inside a <Method> is silently ignored, so the method
is built from the structured <interface> instead — which produced a
malformed FB_init that failed in four different ways at once. For FB_init
that means <returnType><BOOL /></returnType> plus bInitRetains,
bInCopyCode and your own parameters as <inputVars>.
qualified_only is set on every GVL here (GVL_MQTT,
GVL_DALI, GVL_PERSISTENT, GVL_DMX, GVL_RS485), so a POU
body must write GVL_MQTT.fbMqttPublishQueue, never the bare name.
FB_init of its own still accepts the
base's FB_init parameters at the declaration site:
inst : FB_DERIVED(sFriendlyName := 'Kitchen');. Declare FB_init once on the
base and the whole hierarchy inherits the parameter.FB_init may read a GVL value and may take ADR() of a GVL member. It
compiles — but runtime initialisation order between a GVL and an instance in
another POU is not something the compiler checks. Prefer storing only literals
passed into FB_init and doing GVL-dependent wiring lazily on the first cycle,
which is order-proof.GVL_MQTT.PLC_Device is declared inside the GVL
with a full FB_init argument list including pMqttPublishQueue := ADR(fbMqttPublishQueue).Auto-instantiation cannot declare a block whose FB_init takes parameters. Two
optional files let you take over:
| File | Effect |
|---|---|
.ai/candidates/_harness.decl | Appended verbatim to the host program's declaration. Give a complete VAR ... END_VAR block. |
.ai/candidates/_harness.impl | Appended verbatim to a body, so you can actually call the block. |
Any candidate block named in these files is counted as covered and drops out of
not_instantiated. Note the programs here keep their logic in actions, so a
program's own body is often empty and exposes no implementation; the harness
falls back to the first action that has one and reports it as impl_host.
Delete them when the work they supported is finished. They are gitignored and
machine-local, they are injected into every verify from then on, and nothing
reports that they exist. Two ways that bites:
verify compiles more than the project does. A harness left over from an
old refactor keeps an otherwise-unreferenced block compiled on your machine
only. Your verify is green, a colleague's or CI's is green for a different
reason, and the shipped project never compiles that block at all. apply
injects no harness, so it is the honest answer to "is this block compiled?"InitRS485(...) failed a run that had just moved that configuration
into FB_init - four errors pointing at a program nobody had touched, in code
that is not in the repository.libs is the way in and out of the Library Manager, which is otherwise only
reachable from the IDE.
./tools/ai/codesys.ps1 libs # what is referenced, and what is installed
./tools/ai/codesys.ps1 libs -LibFilter modbus # also: every installed Modbus library
./tools/ai/codesys.ps1 libs -RemoveLib '#IoDrvModbus'
./tools/ai/codesys.ps1 libs -AddLib 'SysCom, 3.5.17.0 (System)'
./tools/ai/codesys.ps1 libs -UpdateLib 'PRO_JSON' # repoint a PLACEHOLDER at the newest installedRead-only without -RemoveLib / -AddLib / -UpdateLib. With any of them it
behaves like apply: it builds first and refuses to save a project that does
not build, which is what makes "is anything still using this?" a question you
can answer by trying it.
Four things about the report are worth knowing before acting on it:
-LibFilter only sees this machine. The repository query lists what is
installed, not what the CODESYS Store has. "Nothing newer" means "none
here", never "none exists" — installing a newer library is still an IDE job.* version floats. PRO_JSON, * (Pro Electric) already resolves to the
newest installed version, so it is never reported as behind one. What pins it
to something older is a redirection, which the report shows separately.(not resolved in this project) is not outdated. Several visualisation
placeholders carry a default resolution but resolve to nothing, because
nothing uses them. Reporting those as outdated would send you after a version
no build is reading.# included for a
placeholder. libs with no arguments prints the exact strings.-UpdateLib takes a placeholder name only. A fixed reference carries its
version inside its name, so moving one means -RemoveLib then -AddLib.
librarymanagerNot library_manager, which is what the shipped .pyi stub says and what does
not resolve — the same class of stub-versus-reality gap as export_xml's
argument order and insert()'s parameter order. repository_manager() in
codesys_task.py tries the names and then falls back to finding whatever object
answers get_all_libraries, so this should not need rediscovering.
Also: a repository entry's displayname is the full "IoDrvModbus, 4.5.0.0 (CODESYS)" string, not a bare name. Grouping on it directly yields one entry
per version and matches nothing.
device -AddModule plugs a K-bus module in, which was the last part of the
project only the IDE could reach:
./tools/ai/codesys.ps1 device # read-only: the tree
./tools/ai/codesys.ps1 device -AddModule '0287_75x_647' -Under 'Pfc200Bus' -ForceA whole device, including a second controller, goes in the same way — but a device carries its own identification, so it has to be given:
# type:id:version, read out of the device description
./tools/ai/codesys.ps1 device -AddDevice '4096:1006 1209:6.4.5.11' -NodeName 'Wago_PFC200_G2_Virtual' -Force
./tools/ai/codesys.ps1 device -AddDevice '32776:07530647000000002424:2.0.0.20' -Under 'Kbus' -NodeName 'DALI_753_647' -Force
./tools/ai/codesys.ps1 device -RemoveNode '_75x_647' -Force # and back out againA second controller is how an otherwise-uncompiled block gets covered. A
project can hold several devices, and an application that is never downloaded is
still compiled. Adding a controller creates Plc Logic/Application with a
Library Manager and nothing else, so scaffold fills it in:
./tools/ai/codesys.ps1 scaffold -Scaffold tools/ai/scaffold/g2-dali-verify.json -ForceA scaffold spec creates GVLs, programs and tasks inside a named application,
idempotently by name, and it takes *_file fragments exactly as an edits spec
does. Wago_PFC200_G2_Virtual in this project is the worked example.
A task item takes kind, interval, priority and calls, and an item naming
a task that already exists updates it — which is how an existing task's
scheduling is changed without the GUI. tools/ai/scaffold/g1-ping-priority.json
is the priority example and tools/ai/scaffold/mqtt-task-cyclic.json the
kind one, taking MqttCommunication from Freewheeling to Cyclic. Omitted
fields are left alone.
kind is set before interval, because a task that is still Freewheeling
has no period to set and CODESYS may ignore or reject one; and it is read back
afterwards, with a mismatch reported as an error. Which ScriptEngine global
holds the task kinds is undocumented and dir() reveals nothing, so
task_kind_candidates tries every plausible container and spelling and lets the
read-back decide — the same shape as a scaffold item's "set".
Four things learned building that, each of which cost a run:
maxtaskpriority to 15, and CODESYS rejects anything higher with "The task
priority is invalid" without mentioning a range. Read the limit from
<ts:setting name="maxtaskpriority"> in the device description.WagoAppDALI and FB_OUTPUT_DIMMER_DALI_MQTT
with no library reference of its own and compiled. What does not carry over
is a GVL: those belong to an application, so the second one needs its own
GVL_MQTT — only the two constants FB_MQTT_BASE actually reads.dir() on a ScriptEngine object returns nothing, so the usual way of
finding an undocumented member does not work here. probe dumps it anyway, and
it comes back empty; a name has to be tried and read back instead. That is what
a scaffold item's "set" is for.Three things about -AddModule, all of which cost a run to learn:
add takes the parent's
(type, id, version) plus the module's own ModuleId, which is why every
module under Pfc200Bus reports the bus's identification (288,
0000 0001, 4.19.0.0) and differs only in name. The harness therefore reads
the identification off the parent instead of asking for it — there is exactly
one right answer and it is already in the project.C:\ProgramData\CODESYS\Devices\288\0000 0001\4.19.0.0\device.xml, or the copy
under CODESYS Control for PFC200 SL\...\WagoPFC200Internalbus.devdesc.xml;
grep it for <ModuleId>. 0287_75x_647 is the 753-647 DALI multi-master.-Under resolves in three tiers: full path, exact name, path substring.
Without the last two being ranked, Pfc200Bus is ambiguous by way of its own
children, whose paths all contain it — the obvious query refused by the
modules already on the bus. The path tier is there because an exact name stops
identifying anything the moment a project has two controllers: an installation
with two PFCs has two nodes called Pfc200Bus, both real. A substring does not
rescue that either, since Wago_G1_Annex/Pfc200Bus is a substring of every
module already on that bus. Only the whole path is unique, so pass the whole
path — -Under 'Wago_G1_Annex/Pfc200Bus' — on any project with more than one
controller.Writing needs -Force, and like libs it builds first and refuses to save a
project that does not build.
Both were latent for as long as the project had exactly one application, and both made the harness quietly report less than it had checked:
build() is incremental, rebuild() is not. An application CODESYS
considers up to date compiles nothing and says nothing, while still landing in
built. A baseline recorded straight after a save therefore came back with the
new application's messages only. build_and_collect now calls rebuild().The second fix also surfaced the library Information messages (MQTT's TODO
and semaphore notes) that had been swallowed all along. They are in the
baseline now, so they do not read as new.
An unreferenced POU is never compiled. A block imported into the project-level POU pool belongs to no application, so CODESYS generates no code for it and reports a successful build for code that does not compile. This is the most dangerous failure mode in the whole loop.
verify works around it by declaring an instance of each candidate function
block inside a program the task configuration already calls, which forces a full
check of the body. Two cases cannot be handled that way and are reported instead:
pouType other than functionBlock (a function or program is only compiled
where it is called), andFB_init that takes parameters, since the instance cannot be declared
without supplying them.Those appear as NOT COMPILE-CHECKED in the console and under
harness.not_instantiated in the report, and the result line degrades to
BUILD OK, but N candidate block(s) were not compile-checked. Treat that as
unverified. Exercise such a block from a real call site instead. Note that
apply injects no harness at all, by design — verify before you apply.
Compile-checking is the automated gate. Behavioural testing needs real hardware. Don't rediscover this the hard way:
simulate is implemented and mechanically works — it enables simulation on a
sandbox copy, builds, and would log in, start, and run a JSON test spec. But this
project cannot run in simulation. Enabling simulation retargets the application
from the PFC200's 32-bit ARM runtime to the in-process Windows simulation
runtime, which is 64-bit, and the vendored SysFile23 / SysSocket23
compatibility libraries that the MQTT library needs are 32-bit only:
The Library 'syssocket23, 3.5.13.0 (system)' is only supported in 32 bit applicationsThere is no 32-bit IDE in this install, and the one Windows runtime present is
the x64 CODESYS Control Win V3 service, so nothing here can host a 32-bit
application. The task is kept for the diagnosis and in case the library
situation changes.
scan relies on a gateway broadcast and is not reliable — it listed three PLCs
once and nothing at all twenty minutes later, on a machine with two NICs on the
same subnet. When it comes up empty, find the PLC out-of-band instead: arp -a
filtered on WAGO's 00-30-de MAC prefix maps IPs to the device names the CODESYS
scan reports, and Test-NetConnection <ip> -Port 11740 confirms the runtime is
listening. Then use download -Ip <ip>, which resolves the node through the
gateway directly and needs no broadcast.
The spec format, for when it is usable — or as the model for a device-side test:
{ "steps": [
{ "label": "press", "write": {"GVL_MQTT.clientID": "'test'"}, "delay_ms": 300 },
{ "label": "assert", "expect": {"GVL_MQTT.clientID": "test"} }
] }Run with ./tools/ai/codesys.ps1 simulate -Spec path\to\spec.json. Values are
strings in both directions, and download -Spec runs the same format against real
hardware. A failed expect lands in test_failures and fails the run, distinct
from a compiler error.
An expect is compared against the value as the runtime prints it, so it needs
the type prefix and, for a string, the apostrophes: INT#4, UDINT#1,
TIME#10s, 'probes=1 found=9600/255'. Writing 4 against an INT reports
expected 4, got INT#4, which reads as a behavioural failure and is nothing of the
kind — and each retry is a full download, so it costs three minutes to learn twice.
The compiler is the only automated gate on the PLC side, but the broker sees everything the PLC publishes — which is where the runtime checks live. Use them around every download:
./tools/ai/Mqtt-Snapshot.ps1 -Out .ai/mqtt/before.txt # BEFORE the download
./tools/ai/codesys.ps1 download -Force -Ip 10.101.1.232
./tools/ai/Mqtt-Snapshot.ps1 -Out .ai/mqtt/after.txt # after, once settled
./tools/ai/Mqtt-Snapshot.ps1 -Diff .ai/mqtt/before.txt,.ai/mqtt/after.txtThe snapshot captures retained topics only (--retained-only), so it is
reproducible rather than a race: discovery configs and last-known states survive a
reconnect. Topics are sorted, so two snapshots diff cleanly. -Watch prints live
traffic instead, for checking that a pushbutton event actually publishes.
Read the diff's GONE section first. A discovery config that was retained
before and is absent now means an entity Home Assistant still shows and nothing
publishes to any more — exactly the silent regression an EntityType mistake
causes, and exactly what a clean compile cannot tell you.
A snapshot diff cannot answer that, and the difference matters: a retained config
looks identical whether the PLC wrote it a second ago or a year ago. So a broken
publish path is invisible to a diff — the broker stays fully populated with stale
configs while the PLC announces nothing. That is not hypothetical; it is what a
RIGHT() on a STRING(1500) did to every entity on the lab PLC for weeks.
check_mqtt_discovery.py answers both halves. It validates the structure of every
config (valid JSON, not truncated, uniq_id present, unique, and matching its own
topic, a device with ids, something to talk to), and it separates a genuine publish
from the retained backlog using the retain flag — live traffic arrives with
retain=0, the broker's replay with retain=1.
# 1. record what Home Assistant currently knows
py tools/ai/check_mqtt_discovery.py --device Wago_PFC200_G1_Lab \
--snapshot .ai/mqtt/expect.json
# 2. watch, and download while it runs (background one of them)
py tools/ai/check_mqtt_discovery.py --watch 240 --expect .ai/mqtt/expect.json \
--device Wago_PFC200_G1_Lab
# structure only, against whatever is retained now - needs no PLC
py tools/ai/check_mqtt_discovery.pyIt also fails on a composed truncated json / failed json root strip /
had empty MqttJSON line appearing live on the diagnostic log topic, because that
is how the PLC reports a config it refused to publish — and nothing else reads it.
%r is the retain flag; %R is the response topic. Getting that wrong makes
every message parse as a live publish, so a watch against a powered-down PLC
reports full coverage and passes. The tool now refuses a flag that is not 0
or 1 rather than guessing.
Broker for this project is 10.101.1.11:1883 (GVL_MQTT.broker), and the
trees worth watching are homeassistant/# and Devices/PLC/Lab/#
(GVL_MQTT.MqttBaseTopic). Credentials via -User / -Password if the
broker needs them.
download performs a full download of src/HomeAutomation.project to the
physical PLC and starts it. A download stops the running application and
re-initialises non-persistent variables, and this project drives HVAC, a burner,
covers and lights. So:
-Force is required. Without it the task refuses and explains why.-NoStart loads without starting, -BootApplication also writes a
boot application so the PLC comes back up running this project../tools/ai/codesys.ps1 scan # what is actually reachable
./tools/ai/codesys.ps1 download -Force -Address 003E # target one node, this run onlyThe stored address goes stale. The committed project points at node 00E8,
which no longer answers; a scan found three PFC200s on 003C–003E. Run scan
first and check the runtime column — only a device reporting CODESYS Control for
PFC200 SL matches this project; the WAGO 750-8202 entries run a different
runtime. -Address (node, as scan prints it) and -Ip retarget for a single
run without touching the committed settings.
Credentials come from $env:PLC_USER / $env:PLC_PASS when set, otherwise
CODESYS's cached device login is used. They are passed through the task file in
gitignored .ai/, which the wrapper shreds immediately after the run. Never put
a device password in a committed file.
.ai/reports/<task>.json:
| Key | Meaning |
|---|---|
ok | No tool errors and no compiler errors. |
messages[] | severity, text, object, position, number, occurrences. |
error_count / warning_count | Counts over messages. |
imports[] | Per candidate file: added, replaced, skipped, errors. |
harness | host, instantiated, not_instantiated — read this. |
built | Applications that compiled. |
errors[] | Tool-level failures, distinct from compiler messages. |
test_failures[] | Failed expect assertions from a simulate spec. |
online | simulate only: login state, application/operating state, per-step results. |
devices / gateways | device only: configured target and known gateways. |
occurrences counts identical messages collapsed across message categories, so
a mismatch against CODESYS's own "N errors, M warnings" line is explainable
rather than a sign that something was hidden.
A script cannot position a module on a bus, and cannot see where one sits.
Three separate attempts, so nobody repeats them:
insert(name, index, ...) is the order the binding accepts (index-first is
refused) and it ignores the index and appends; removing a terminal and
re-adding it does not move it either - the saved project reloads in exactly
the order it went in; and the order get_children(False) reports is creation
order, not rail position. The PLCopen export's ProjectStructure agrees with
get_children, so it is not independent confirmation of anything.
The trap this sets is the expensive part. The IDE showed three RTD terminals
side by side while both scripted views reported them split apart by two other
modules, and a guard built on the scripted view refused a correct project.
Position on a K-bus decides which process-image words a terminal reads, so
getting it wrong is silent and serious - but it can only be checked and fixed in
the IDE's device tree. add_module now prints an ADVISORY saying so whenever an
index is asked for, and reports the creation order labelled as such. Adding a
terminal at the far right of the rail avoids the question entirely.
I/O channel mapping IS scriptable, which the skill assumed it was not.
A terminal's channels live on its child connector's host_parameters, not on
device_parameters, and each one that answers is_mappable_io carries an
io_mapping whose variable setter names it - an unqualified name creates the
variable, exactly as typing one into the IDE's mapping editor does. device -MapIo <spec.json> does it in bulk. Read the value back afterwards: the setter
takes any expression that parses, so a typo is accepted quietly.
The shipped .pyi stubs have wrong argument orders. export_xml and
import_xml really take reporter first, and the .NET binding rejects
those names as keywords — pass them positionally.
Reporter classes must subclass the injected ImportReporter /
ExportReporter. A duck-typed class is refused with expected IExportReporter.
Folders are not exportable. Passing one as an export root makes CODESYS skip its entire subtree, which silently drops every function block. The driver walks through folders and passes leaf objects.
IronPython 2.7, not CPython. No f-strings; its json refuses .NET integers
and ASCII-encodes text, which non-ASCII compiler messages break. Everything
entering the report goes through the u() helper.
Settle any further API question with probe, which dumps the real .NET
overloads, rather than guessing across 80-second round trips.
Re-exporting an unchanged project reproduces the committed
src/Exports/PLCopen.xml byte for byte except two header timestamps, so
expect a 2-line diff and nothing more. A larger diff means the project really
changed. Object order is sorted deliberately — with _ folded below
alphanumerics, matching the order CODESYS itself produced — so the file stays
diffable.
© MichielVanwelsenaere, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/codesys-loop of MichielVanwelsenaere/HomeAutomation.CoDeSys3.
Open the folder on GitHubat commit 924c3da
Codesys Loop next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Codesys Loop this skillMichielVanwelsenaere/HomeAutomation.CoDeSys3 | 147 | — | ~11k | Automated safety check: Pass | MIT | |
| Guidelinesakash-network/node | 1.1k | 22 repos | ~577 | Automated safety check: Pass | MIT | |
| Component Refactoringlangflow-ai/langflow | 156k | — | ~3.5k | Automated safety check: Pass | MIT | |
| Migrate Core Code to Submodulestinyhumansai/openhuman | 42k | — | ~2.6k | Automated safety check: Pass | GPL-3.0 | |
| Systematic Code Refactoringluongnv89/claude-howto | 42k | — | ~3k | Automated safety check: Pass | MIT | |
| Codexskills-directory/skill-codex | 1.5k | 3 repos | ~1.8k | Automated safety check: Pass | MIT |
akash-network/node
Behavioral guidelines to reduce common LLM coding mistakes. An agent skill from akash-network/node.
langflow-ai/langflow
Refactor high-complexity React components in Langflow frontend.
tinyhumansai/openhuman
Plans and carries out moving non-host-specific code and its tests from the OpenHuman core into vendored tiny submodule libraries, then releases the submodule and re-pins the host.
luongnv89/claude-howto
Guides refactoring in phases based on Martin Fowler's method: research, test coverage check, planning and small tested steps, with your approval at each phase.
skills-directory/skill-codex
A skill your agent uses when the user asks to run Codex CLI (codex exec, codex resume) or references OpenAI Codex for code analysis, refactoring, or automated editing
DavidObando/gsharp
Forces the laziest solution that actually works, simplest, shortest, most minimal.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Regenerate the machine-owned parts of the function block docs from the PLCopen export, scaffold a page for a new function block, and check whether the docs still match the code.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Bring an installation's CODESYS project up to date with this reference project's function blocks, without changing what that installation does.
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Find out why a PFC200 stopped - an application that died, a building that went dead, an exception, a crash, or a restart nobody explained - by reading the runtime's own log and core dump on the…
MichielVanwelsenaere/HomeAutomation.CoDeSys3
Exercise the PLC's actual behaviour on real hardware - lights, pushbuttons, covers, dimmers and the HVAC chain - by commanding it over MQTT and asserting the result on the broker and in the running…
Categories
Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine. CoDeSys3. Read, write and compile-check CODESYS code in this project without opening the GUI, by driving CODESYS headlessly through its ScriptEngine.
Codesys Loop fits situations like: changing a function block; refactoring ST code; checking whether a change compiles; re-exporting src/Exports/PLCopen.xml.
Run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a claude-code`. Or copy the skill folder (.claude/skills/codesys-loop in MichielVanwelsenaere/HomeAutomation.CoDeSys3) into .claude/skills/codesys-loop in your project. Claude Code loads it when a task matches its description.
Run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a codex`. Or copy the skill folder (.claude/skills/codesys-loop in MichielVanwelsenaere/HomeAutomation.CoDeSys3) into .agents/skills/codesys-loop in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add MichielVanwelsenaere/HomeAutomation.CoDeSys3 --skill codesys-loop -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/codesys-loop, .gemini/skills/codesys-loop, .github/skills/codesys-loop and .opencode/skills/codesys-loop in your project.
Going by SKILL.md and its folder, Codesys Loop needs the command-line tools its instructions call (winget and git). Our summary lists: Python 3.
SKILL.md names 2 domains. As links in the text: 3s-software.com and w3.org. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Codesys Loop is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 46k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Codesys Loop: Guidelines (akash-network/node, 1.1k stars), Component Refactoring (langflow-ai/langflow, 156k stars), Migrate Core Code to Submodules (tinyhumansai/openhuman, 42k stars) and Systematic Code Refactoring (luongnv89/claude-howto, 42k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
MichielVanwelsenaere (a GitHub user) maintains it in MichielVanwelsenaere/HomeAutomation.CoDeSys3, which has 147 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on September 14, 2026.
Source: MichielVanwelsenaere/HomeAutomation.CoDeSys3 on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.