Intr-un articol anterior, am descris cum am invatat un LLM sa citeasca si sa scrie fisierele XEF ale Schneider Electric — formatul XML din spatele PLC-urilor M340. Abordarea a functionat pentru ca fisierele XEF sunt text. Export, editare, import.
<p>Dar pentru proiectul nostru de statie de betoane care ruleaza pe un Schneider M241 (TM241CE40R), trecusem la EcoStruxure Machine Expert — un instrument complet diferit, bazat pe CODESYS 3.5. Si fisierele de proiect Machine Expert sunt binare. Fara export text implicit. Fara scheme XSD. Fara o cale evidenta de intrare.</p>
<p>Intrebarea era: putem oferi unui LLM acelasi nivel de acces la Machine Expert pe care il aveam cu Control Expert? Nu doar citirea codului, ci si modificarea lui, compilarea si, in cele din urma, descarcarea pe un PLC real — totul dintr-un terminal Linux, prin SSH, printr-un agent AI?</p>
<p>Raspunsul s-a dovedit a fi da, dar drumul pana acolo a fost mai interesant decat ne asteptam.</p>
<h2>Trei abordari, testate in paralel</h2>
<p>Am inceput prin investigarea tuturor modurilor posibile de a controla Machine Expert de la distanta. Trei directii de cercetare in paralel:</p>
<p><strong>Abordarea 1: Scriptarea GUI-ului aflat in executie.</strong> Machine Expert are un panou «Scripting Immediate» — un REPL IronPython 2.7 cu acces complet la API-ul de scripting CODESYS. Am fi putut alimenta comenzi din exterior?</p>
<p>Am descoperit ca Machine Expert (LogicBuilder.exe) nu expune obiecte COM, named pipes sau redirectare in linia de comanda. Flag-ul <code>--runscript</code> lanseaza o instanta <em>noua</em> in loc sa trimita catre cea in executie. Singura modalitate de a executa un script in IDE-ul activ este prin prompt-ul <code>>>></code> din GUI.</p>
<p>Folosind Windows UI Automation (task-uri programate cu tipul de logon <code>Interactive</code> + <code>AutomationElement</code> pentru a gasi campul de input <code>>>></code>), am reusit sa facem paste si sa executam comenzi <code>execfile()</code> in mod programatic. Fragil, dar a functionat.</p>
<p><strong>Abordarea 2: Rulare headless.</strong> Machine Expert vine cu <code>LogicBuilderShell.exe</code> — o varianta headless proiectata pentru CI/CD. Accepta <code>--noUI --runscript</code> si ar fi trebuit sa fie perfecta pentru automatizare.</p>
<p>A refuzat sa porneasca. VM-ul nu avea licenta Schneider Electric activata (rula in modul demo), iar dialogul de mod demo necesita o sesiune GUI. Varianta Shell a crapat cu:</p>
System.InvalidOperationException: Showing a modal dialog box or form when the
application is not running in UserInteractive mode is not a valid operation. <p>Fundatura fara o licenta platita.</p>
<p><strong>Abordarea 3: Server HTTP in interiorul Machine Expert.</strong> Ce-ar fi daca am porni un server web <em>in interiorul</em> mediului IronPython al IDE-ului? Motorul de scripting are acces la <code>BaseHTTPServer</code> din Python 2.7, iar CODESYS suporta explicit fire de executie in fundal prin <code>system.execute_on_primary_thread()</code>.</p>
<p>Aceasta a fost varianta castigatoare.</p>
<h2>Arhitectura</h2>
<p>Solutia este un singur script Python 2.7 care ruleaza in interiorul motorului IronPython al Machine Expert. Cand este executat prin panoul Scripting Immediate, porneste un server HTTP pe un fir de executie in fundal:</p>
[Linux / Claude Code] --HTTP--> [Windows VM: Machine Expert + Bridge pe portul 8090]
|
v (API scripting IronPython)
[Motor CODESYS]
|
v (Modbus TCP prin port proxy)
[PLC TM241CE40R] <p>Bridge-ul expune un REST API:</p>
GET /api/status - Cale proiect, dirty flag, nume aplicatie
GET /api/tree - Lista tuturor obiectelor (POU-uri, GVL-uri, tipuri)
GET /api/st/read?name=X - Citire declaratie + implementare a oricarui POU
POST /api/st/write - Modificare cod ST al oricarui POU
POST /api/build - Compilare aplicatie
POST /api/xml/import - Import PLCopen XML
POST /api/xml/export - Export obiecte ca PLCopen XML
POST /api/project/save - Salvare proiect
POST /api/shutdown - Oprire bridge <p>Fiecare operatie care atinge proiectul este marshaled catre firul principal prin <code>system.execute_on_primary_thread()</code>, in timp ce serverul HTTP ruleaza pe un fir daemon. Acest lucru este suportat explicit de CODESYS, dar marcat «Experts Only» in documentatia lor.</p>
<h2>Capcana encoding-ului</h2>
<p>Prima versiune a functionat imediat pentru operatii simple — <code>/api/ping</code>, <code>/api/status</code>, <code>/api/build</code> au returnat JSON curat. Dar cand am incercat sa citim cod ST dintr-un POU, raspunsul era gol.</p>
<p>Eroarea din consola Machine Expert a spus totul:</p>
UnicodeDecodeError: 'unknown' codec can't decode byte 0xB3 at position 8673 <p>CODESYS returneaza obiecte .NET <code>System.String</code> care contin caractere din codepage-ul Windows (in acest caz, <code>³</code> din unitatea <code>kg/m³</code> dintr-un comentariu de cod). Modulul <code>json</code> din Python 2.7 a incercat sa le decodeze ca UTF-8 si a esuat.</p>
<p>Solutia a fost un sanitizer recursiv care forteaza fiecare string prin <code>unicode()</code> cu fallback <code>xmlcharrefreplace</code>:</p>
def _sanitize(obj):
if isinstance(obj, dict):
return {_sanitize(k): _sanitize(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_sanitize(item) for item in obj]
if isinstance(obj, bool) or isinstance(obj, (int, float)) or obj is None:
return obj
try:
s = unicode(obj)
return s.encode("ascii", "xmlcharrefreplace").decode("ascii")
except:
return u"<encoding error>" <p>Dupa aceasta modificare, fiecare POU — inclusiv cele cu diacritice romanesti si simboluri de unitati de masura — s-a serializat corect in JSON.</p>
<figure class="blog-image">
<img src="/blog/img/me_bridge_scripting_immediate.png" alt="Serverul HTTP ME Bridge ruland in panoul Scripting Immediate al Machine Expert" loading="lazy">
<figcaption>Serverul HTTP ME Bridge ruland in panoul Scripting Immediate al Machine Expert — o singura comanda execfile() porneste un API HTTP pe portul 8090 cu acces complet la motorul de scripting CODESYS.</figcaption>
</figure>
<h2>Ce putem face cu el</h2>
<p>Cu bridge-ul in functiune, un agent AI de pe Linux are acces programatic complet la un proiect Machine Expert. Iata cum arata un flux de lucru tipic:</p>
<p><strong>Citirea intregului cod PLC:</strong></p>
$ curl -s http://<vm-ip>:8090/api/tree?recursive=true | python3 -m json.tool
{
"objects": [
{"name": "Continuous_Dosing_Control", "has_declaration": true, "has_implementation": true},
{"name": "FB_BeltScale", "has_declaration": true, "has_implementation": true},
{"name": "GVL_Dosing", "has_declaration": true},
...
],
"count": 55
}
$ curl -s "http://<vm-ip>:8090/api/st/read?name=Continuous_Dosing_Control"
{
"name": "Continuous_Dosing_Control",
"declaration": "PROGRAM Continuous_Dosing_Control\nVAR\n max_component: REAL; ...",
"implementation": "(* Recipe calculations *)\nrecipe_density := aggregate1_target + ..."
} <p><strong>Modificarea codului si compilarea:</strong></p>
$ curl -X POST -d '{"name":"MAIN","implementation":"PRG_ModbusComm();\nPRG_Control();"}' \
http://<vm-ip>:8090/api/st/write
{"name": "MAIN", "updated": ["implementation"]}
$ curl -X POST http://<vm-ip>:8090/api/build
{"result": "build_complete", "message_count": 0, "messages": []} <p><strong>Exportul PLCopen XML pentru version control:</strong></p>
$ curl -X POST -d '{"name":"Application","path":"C:\\export.xml","recursive":true}' \
http://<vm-ip>:8090/api/xml/export
{"result": "exported", "path": "C:\\export.xml"} <h2>Utilizare reala: analiza si modificarea unui algoritm de dozare</h2>
<p>Bridge-ul si-a dovedit valoarea imediat. Trebuia sa intelegem si sa modificam algoritmul de dozare continua pentru o statie de betoane — un program PLC care calculeaza vitezele benzilor din targeturile retetei, cu factori de corectie VFD si urmarire automata a cantitatilor.</p>
<p>Folosind bridge-ul, agentul AI:</p>
<ol>
<li><strong>A citit toate cele 12 POU-uri si 6 GVL-uri</strong> din proiectul activ, intelegand fluxul complet de date de la intrarile retetei prin calculele de viteza pana la iesirile VFD.</li>
<li><strong>A identificat o problema de proiectare</strong>: calculul de viteza nu tinea cont de capacitatile diferite ale benzilor. O banda care livra 72.000 kg/h la 100% si un snec de ciment care livra 19.000 kg/h la 100% ar fi produs proportii gresite daca primeau acelasi setpoint procentual.</li>
<li><strong>A propus si verificat o corectie</strong>: normalizarea targeturilor retetei prin valorile de calibrare inainte de calculul rapoartelor de viteza. Sapte linii de Structured Text, verificate contra calculelor din spreadsheet-ul pe care operatorul statiei il pregatise independent.</li>
<li><strong>A recitit codul dupa ce operatorul a aplicat modificarile</strong> in Machine Expert, confirmand ca modificarea era corecta inainte de descarcarea pe PLC.</li>
</ol>
<p>Toate acestea s-au intamplat prin SSH, cu agentul AI ruland pe un laptop Linux conectat printr-un VPN WireGuard la reteaua statiei. PLC-ul era accesibil printr-un port proxy pe calculatorul SCADA, iar Machine Expert rula intr-o masina virtuala KVM.</p>
<h2>Bootstrap: singurul pas manual</h2>
<p>Bridge-ul necesita o singura actiune manuala: deschiderea panoului Scripting Immediate din Machine Expert si tastarea:</p>
execfile(r"C:\Users\Adi\me_bridge_server.py") <p>Dupa aceea, totul este remote. Bridge-ul supravietuieste atat timp cat Machine Expert este deschis. Daca Machine Expert reporneste, comanda trebuie reintrodusa — sau automatizata prin UI Automation folosind un task programat.</p>
<p>Am investigat automatizarea acestui pas prin Windows UI Automation (gasirea campului de input <code>>>></code> dupa <code>AutomationId</code>, setarea focusului, paste prin clipboard, trimiterea Enter). Functioneaza, dar este fragil si necesita o sesiune desktop interactiva. Deocamdata, bootstrap-ul manual este acceptabil — este o actiune unica per sesiune Machine Expert.</p>
<h2>API-ul de scripting CODESYS</h2>
<p>Bridge-ul functioneaza pentru ca CODESYS livreaza un API de scripting IronPython cuprinzator. Obiectele cheie injectate in namespace-ul scriptului:</p>
<ul>
<li><strong><code>projects</code></strong> — Deschidere, salvare, inchidere proiecte. <code>projects.primary</code> returneaza proiectul activ.</li>
<li><strong><code>system</code></strong> — Message store, threading, control UI. <code>system.execute_on_primary_thread()</code> este metoda critica pentru operatii thread-safe.</li>
<li><strong><code>online</code></strong> — Conexiune PLC, login, download, start/stop. <code>online.create_online_application()</code> ofera acces la citirea/scrierea variabilelor live.</li>
</ul>
<p>API-ul este documentat prin fisiere stub <code>.pyi</code> din directorul de instalare:</p>
LogicBuilder/ScriptLib/Stubs/scriptengine/
├── ScriptProject.pyi # Operatii proiect, import/export XML
├── ScriptApplication.pyi # Build, rebuild, clean, boot application
├── ScriptOnline.pyi # Conexiune PLC, download, acces variabile
├── ScriptTextualObject.pyi # Citire/scriere cod ST
├── ScriptTreeObject.pyi # Navigare arbore proiect
└── ScriptSystem.pyi # Threading, mesaje, UI <p>Aceste stub-uri sunt echivalentul Machine Expert al schemelor XSD din Control Expert — cheia nedocumentata care face posibil accesul programatic.</p>
<h2>Ce urmeaza</h2>
<p>Bridge-ul deschide posibilitati pe care inca le exploram:</p>
<ul>
<li><strong>Testare automata de regresie</strong>: modifica un POU, compileaza, verifica erorile, revino — totul intr-un script.</li>
<li><strong>Monitorizare variabile live</strong>: metoda <code>ScriptOnlineApplication.read_value()</code> poate citi variabile PLC in timp real, permitand debugging asistat de AI.</li>
<li><strong>Deployment continuu</strong>: modifica codul, compileaza, descarca pe PLC, verifica — un pipeline CI/CD pentru automatizari industriale.</li>
<li><strong>Management multi-proiect</strong>: acelasi pattern de bridge ar trebui sa functioneze pentru orice IDE bazat pe CODESYS 3.5 (ABB Automation Builder, WAGO e!COCKPIT, etc.) — netestat, dar API-ul de scripting este comun.</li>
</ul>
<p>Concluzia fundamentala este aceeasi ca la lucrul nostru cu Control Expert: instrumentele de automatizare industriala au mai mult acces programatic decat promoveaza producatorii lor. Trebuie doar sa te uiti in directorul de instalare.</p>
<hr>
<p class="blog-endnote">Machine Expert HTTP Bridge este un script IronPython 2.7 (~500 linii) care ruleaza in EcoStruxure Machine Expert V2.3. Nu necesita nicio licenta suplimentara. <a href="/blog/me_bridge_server.py" download>Descarca scriptul bridge (me_bridge_server.py)</a>.</p>
<p class="blog-endnote">Construim sisteme de automatizare pentru statii de asfalt si betoane — software de comanda, programare PLC, tablouri electrice si retrofit al sistemelor vechi. Daca aveti de-a face cu sisteme de comanda al caror producator nu mai ofera suport de ani buni, <a href="/#contact">contactati-ne</a>.</p>