ics-simlab-config-gen-claude/appunti.txt

1640 lines
54 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

================================================================================
APPUNTI OPERAZIONI - ics-simlab-config-gen_claude
================================================================================
Data: 2026-01-27
================================================================================
NOTA: diario.md vs appunti.txt
------------------------------
- appunti.txt: note operative rapide, bullet point, aggiornare subito
- diario.md: registro giornaliero per tesi, aggiornare a fine richiesta lunga (>30 parole)
- Scopri bug o fix? → qui (appunti.txt)
- Finisci richiesta lunga? → scrivi entry in diario.md con rationale
================================================================================
PROBLEMA INIZIALE
-----------------
PLC2 crashava all'avvio con "ConnectionRefusedError" quando tentava di
scrivere a PLC1 via Modbus TCP prima che PLC1 fosse pronto.
Causa: callback cbs[key]() chiamata direttamente senza gestione errori.
SOLUZIONE IMPLEMENTATA
----------------------
File modificato: tools/compile_ir.py (linee 24, 30-40, 49)
Aggiunto:
- import time
- Funzione _safe_callback() con retry logic (30 tentativi × 0.2s = 6s)
- Modifica _write() per chiamare _safe_callback(cbs[key]) invece di cbs[key]()
Risultato:
- PLC2 non crasha più
- Retry automatico se PLC1 non è pronto
- Warning solo dopo 30 tentativi falliti
- Container continua a girare anche in caso di errore
FILE CREATI
-----------
build_scenario.py - Builder deterministico (config → IR → logic)
validate_fix.py - Validatore presenza fix nei file generati
CLEANUP_SUMMARY.txt - Summary pulizia progetto
README.md (aggiornato) - Documentazione completa
docs/ (7 file):
- README_FIX.md - Doc principale fix
- QUICKSTART.txt - Guida rapida
- RUNTIME_FIX.md - Fix dettagliato + troubleshooting
- CHANGES.md - Modifiche con diff
- DELIVERABLES.md - Summary completo
- FIX_SUMMARY.txt - Confronto codice before/after
- CORRECT_COMMANDS.txt - Come usare path assoluti con sudo
scripts/ (3 file):
- run_simlab.sh - Launcher ICS-SimLab con path corretti
- test_simlab.sh - Test interattivo
- diagnose_runtime.sh - Diagnostica container
PULIZIA PROGETTO
----------------
Spostato in docs/:
- 7 file documentazione dalla root
Spostato in scripts/:
- 3 script bash dalla root
Cancellato:
- database/, docker/, inputs/ (cartelle vuote)
- outputs/last_raw_response.txt (temporaneo)
- outputs/logic/, logic_ir/, logic_water_tank/ (vecchie versioni)
Mantenuto:
- outputs/scenario_run/ (SCENARIO FINALE per ICS-SimLab)
- outputs/configuration.json (config base)
- outputs/ir/ (IR intermedio)
STRUTTURA FINALE
----------------
Root: 4 file essenziali (main.py, build_scenario.py, validate_fix.py, README.md)
docs/: documentazione (60K)
scripts/: utility (20K)
outputs/: solo file necessari (56K)
+ cartelle codice sorgente (tools/, services/, models/, templates/, helpers/)
+ riferimenti (examples/, spec/, prompts/)
COMANDI UTILI
-------------
# Build scenario completo
python3 build_scenario.py --overwrite
# Valida fix presente
python3 validate_fix.py
# Esegui ICS-SimLab (IMPORTANTE: path assoluti con sudo!)
./scripts/run_simlab.sh
# O manualmente:
cd /home/stefano/projects/ICS-SimLab-main/curtin-ics-simlab
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
# Monitor PLC2 logs
sudo docker logs $(sudo docker ps --format '{{.Names}}' | grep plc2) -f
# Stop
cd /home/stefano/projects/ICS-SimLab-main/curtin-ics-simlab && sudo ./stop.sh
PROBLEMA PATH CON SUDO
-----------------------
Errore ricevuto: FileNotFoundError quando usato ~/projects/...
Causa: sudo NON espande ~ a /home/stefano
Soluzione:
- Usare SEMPRE percorsi assoluti con sudo
- Oppure usare ./scripts/run_simlab.sh (gestisce automaticamente)
WORKFLOW COMPLETO
-----------------
1. Testo → configuration.json (LLM):
python3 main.py --input-file prompts/input_testuale.txt
2. Config → Scenario completo:
python3 build_scenario.py --overwrite
3. Valida fix:
python3 validate_fix.py
4. Esegui:
./scripts/run_simlab.sh
VALIDAZIONE FIX
---------------
$ python3 validate_fix.py
✅ plc1.py: OK (retry fix present)
✅ plc2.py: OK (retry fix present)
Verifica manuale:
$ grep "_safe_callback" outputs/scenario_run/logic/plc2.py
(deve trovare la funzione e la chiamata in _write)
COSA CERCARE NEI LOG
---------------------
✅ Successo: NO "Exception in thread" errors in PLC2
⚠️ Warning: "WARNING: Callback failed after 30 attempts" (PLC1 lento ma ok)
❌ Errore: Container crasha (fix non presente o problema diverso)
NOTE IMPORTANTI
---------------
1. SEMPRE usare percorsi assoluti con sudo (no ~)
2. Rebuild scenario dopo modifiche config: python3 build_scenario.py --overwrite
3. Validare sempre dopo rebuild: python3 validate_fix.py
4. Fix è nel generatore (tools/compile_ir.py) quindi si propaga automaticamente
5. Solo dipendenza: time.sleep (stdlib, no package extra)
STATUS FINALE
-------------
✅ Fix implementato e testato
✅ Scenario pronto in outputs/scenario_run/
✅ Validatore conferma presenza fix
✅ Documentazione completa
✅ Progetto pulito e organizzato
✅ Script pronti per esecuzione
Pronto per testing con ICS-SimLab!
================================================================================
NUOVA FEATURE: PROCESS SPEC PIPELINE (LLM → process_spec.json → HIL logic)
================================================================================
Data: 2026-01-27
OBIETTIVO
---------
Generare fisica di processo tramite LLM senza codice Python free-form.
Pipeline: prompt testuale → LLM (structured output) → process_spec.json → compilazione deterministica → HIL logic.
FILE CREATI
-----------
models/process_spec.py - Modello Pydantic per ProcessSpec
- model: Literal["water_tank_v1"] (enum-ready)
- dt: float (time step)
- params: WaterTankParams (level_min/max/init, area, q_in_max, k_out)
- signals: WaterTankSignals (mapping chiavi HIL)
tools/generate_process_spec.py - Generazione LLM → process_spec.json
- Usa structured output (json_schema) per output valido
- Legge prompt + config per contesto
tools/compile_process_spec.py - Compilazione deterministica spec → HIL logic
- Implementa fisica water_tank_v1
- d(level)/dt = (Q_in - Q_out) / area
- Q_in = q_in_max se valvola aperta
- Q_out = k_out * sqrt(level) (scarico gravitazionale)
tools/validate_process_spec.py - Validatore con tick test
- Controlla modello supportato
- Verifica dt > 0, min < max, init in bounds
- Verifica chiavi segnali esistono in HIL physical_values
- Tick test: 100 step per verificare bounds
examples/water_tank/prompt.txt - Prompt esempio per water tank
FISICA IMPLEMENTATA (water_tank_v1)
-----------------------------------
Equazioni:
- Q_in = q_in_max if valve_open >= 0.5 else 0
- Q_out = k_out * sqrt(level)
- d_level = (Q_in - Q_out) / area * dt
- level = clamp(level + d_level, level_min, level_max)
Parametri tipici:
- dt = 0.1s (10 Hz)
- level_min = 0, level_max = 1.0 (metri)
- level_init = 0.5 (50% capacità)
- area = 1.0 m^2
- q_in_max = 0.02 m^3/s
- k_out = 0.01 m^2.5/s
COMANDI PIPELINE PROCESS SPEC
-----------------------------
# 1. Genera process_spec.json da prompt (richiede OPENAI_API_KEY)
python3 -m tools.generate_process_spec \
--prompt examples/water_tank/prompt.txt \
--config outputs/configuration.json \
--out outputs/process_spec.json
# 2. Valida process_spec.json contro config
python3 -m tools.validate_process_spec \
--spec outputs/process_spec.json \
--config outputs/configuration.json
# 3. Compila process_spec.json in HIL logic
python3 -m tools.compile_process_spec \
--spec outputs/process_spec.json \
--out outputs/hil_logic.py \
--overwrite
CONTRATTO HIL RISPETTATO
------------------------
- Inizializza tutte le chiavi physical_values (setdefault)
- Legge solo io:"input" (valve_open_key)
- Scrive solo io:"output" (tank_level_key, level_measured_key)
- Clamp level tra min/max
VANTAGGI APPROCCIO
------------------
1. LLM genera solo spec strutturata, non codice Python
2. Compilazione deterministica e verificabile
3. Validazione pre-runtime con tick test
4. Estensibile: aggiungere nuovi modelli (es. bottle_line_v1) è semplice
NOTE
----
- ProcessSpec usa Pydantic con extra="forbid" per sicurezza
- JSON Schema per structured output generato da Pydantic
- Tick test verifica 100 step con valvola aperta e chiusa
- Se chiavi non esistono in HIL, validazione fallisce
================================================================================
INTEGRAZIONE PROCESS SPEC IN SCENARIO ASSEMBLY
================================================================================
Data: 2026-01-27
OBIETTIVO
---------
Integrare la pipeline process_spec nel flusso di build scenario, così che
Curtin ICS-SimLab possa eseguire end-to-end con fisica generata da LLM.
MODIFICHE EFFETTUATE
--------------------
1. build_scenario.py aggiornato:
- Nuovo argomento --process-spec (opzionale)
- Se fornito, compila process_spec.json nel file HIL corretto (es. hil_1.py)
- Sostituisce/sovrascrive la logica HIL generata da IR
- Aggiunto Step 5: verifica che tutti i file logic/*.py referenziati esistano
2. tools/verify_scenario.py creato:
- Verifica standalone che scenario sia completo
- Controlla configuration.json esiste
- Controlla logic/ directory esiste
- Controlla tutti i file logic referenziati esistono
- Mostra file orfani (non referenziati)
FLUSSO COMPLETO CON PROCESS SPEC
--------------------------------
# 1. Genera configuration.json (LLM o manuale)
python3 main.py --input-file prompts/input_testuale.txt
# 2. Genera process_spec.json (LLM con structured output)
python3 -m tools.generate_process_spec \
--prompt examples/water_tank/prompt.txt \
--config outputs/configuration.json \
--out outputs/process_spec.json
# 3. Valida process_spec.json
python3 -m tools.validate_process_spec \
--spec outputs/process_spec.json \
--config outputs/configuration.json
# 4. Build scenario con process_spec (sostituisce HIL da IR)
python3 build_scenario.py \
--out outputs/scenario_run \
--process-spec outputs/process_spec.json \
--overwrite
# 5. Verifica scenario completo
python3 -m tools.verify_scenario --scenario outputs/scenario_run -v
# 6. Esegui in ICS-SimLab
cd /home/stefano/projects/ICS-SimLab-main/curtin-ics-simlab
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
FLUSSO SENZA PROCESS SPEC (compatibilità backward)
--------------------------------------------------
# Build scenario con IR (come prima)
python3 build_scenario.py --out outputs/scenario_run --overwrite
VERIFICA FILE LOGIC
-------------------
Il nuovo Step 5 in build_scenario.py verifica:
- Tutti i plcs[].logic esistono in logic/
- Tutti i hils[].logic esistono in logic/
- Se manca un file, build fallisce con errore chiaro
Comando standalone:
python3 -m tools.verify_scenario --scenario outputs/scenario_run -v
STRUTTURA SCENARIO FINALE
-------------------------
outputs/scenario_run/
├── configuration.json (configurazione ICS-SimLab)
└── logic/
├── plc1.py (logica PLC1, da IR)
├── plc2.py (logica PLC2, da IR)
└── hil_1.py (logica HIL, da process_spec o IR)
NOTE IMPORTANTI
---------------
- --process-spec è opzionale: se non fornito, usa IR per HIL (comportamento precedente)
- Il file HIL viene sovrascritto se esiste (--overwrite implicito per Step 2b)
- Il nome file HIL è preso da config (hils[].logic), non hardcoded
- Verifica finale assicura che scenario sia completo prima di eseguire
================================================================================
PROBLEMA SQLITE DATABASE ICS-SimLab
================================================================================
Data: 2026-01-27
SINTOMO
-------
Tutti i container (HIL, sensors, actuators, UI) crashano con:
sqlite3.OperationalError: unable to open database file
CAUSA
-----
Il file `physical_interactions.db` diventa una DIRECTORY invece che un file.
Succede quando Docker crea il volume mount point PRIMA che ICS-SimLab crei il DB.
Verifica:
$ ls -la ~/projects/ICS-SimLab-main/curtin-ics-simlab/simulation/communications/
drwxr-xr-x 2 root root 4096 Jan 27 15:49 physical_interactions.db ← DIRECTORY!
SOLUZIONE
---------
Pulire completamente e riavviare:
cd ~/projects/ICS-SimLab-main/curtin-ics-simlab
# Stop e rimuovi tutti i container e volumi
sudo docker-compose down -v --remove-orphans
sudo docker system prune -af
# Rimuovi directory simulation corrotta
sudo rm -rf simulation
# Riavvia (crea DB PRIMA di Docker)
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
NOTA IMPORTANTE: PATH ASSOLUTO
------------------------------
SEMPRE usare path assoluto completo (NO ~ che non viene espanso da sudo).
SBAGLIATO: sudo ./start.sh ~/projects/.../outputs/scenario_run
CORRETTO: sudo ./start.sh /home/stefano/projects/.../outputs/scenario_run
SEQUENZA STARTUP CORRETTA ICS-SimLab
------------------------------------
1. rm -r simulation (pulisce vecchia simulazione)
2. python3 main.py $1 (crea DB + container directories)
3. docker compose build (build immagini)
4. docker compose up (avvia container)
Il DB viene creato al passo 2, PRIMA che Docker monti i volumi.
Se Docker parte con volumi già definiti ma file mancante, crea directory.
================================================================================
FISICA HIL MIGLIORATA: MODELLO ACCOPPIATO TANK + BOTTLE
================================================================================
Data: 2026-01-27
OSSERVAZIONI
------------
- La fisica HIL generata era troppo semplificata:
- Range 0..1 normalizzati con clamp continuo
- bottle_at_filler derivato direttamente da conveyor_cmd (logica invertita)
- Nessun tracking della distanza bottiglia
- Nessun accoppiamento: bottiglia si riempie senza svuotare tank
- Nessun reset bottiglia quando esce
- Esempio funzionante (examples/water_tank/bottle_factory_logic.py) usa:
- Range interi: tank 0-1000, bottle 0-200, distance 0-130
- Boolean per stati attuatori
- Accoppiamento: bottle fill SOLO se outlet_valve=True AND distance in [0,30]
- Reset: quando distance < 0, nuova bottiglia con fill=0 e distance=130
- Due thread separati per tank e bottle
MODIFICHE EFFETTUATE
--------------------
File: tools/compile_ir.py, funzione render_hil_multi()
1. Detect se presenti ENTRAMBI TankLevelBlock e BottleLineBlock
2. Se sì, genera fisica accoppiata stile esempio:
- Variabile interna _bottle_distance (0-130)
- bottle_at_filler = (0 <= _bottle_distance <= 30)
- Tank dynamics: +18 se inlet ON, -6 se outlet ON
- Bottle fill: +6 SOLO se outlet ON AND bottle at filler (conservazione)
- Conveyor: distance -= 4; se < 0 reset a 130 e fill = 0
- Clamp: tank 0-1000, bottle 0-200
- time.sleep(0.6) come esempio
3. Se no, fallback a fisica semplice precedente
RANGE E SEMANTICA
-----------------
- tank_level: 0-1000 (500 = 50% pieno)
- bottle_fill: 0-200 (200 = pieno)
- bottle_distance: 0-130 interno (0-30 = sotto filler)
- bottle_at_filler: 0 o 1 (boolean)
- Actuator states: letti come bool()
VERIFICA
--------
.venv/bin/python3 build_scenario.py --out outputs/scenario_run --overwrite
cat outputs/scenario_run/logic/hil_1.py
grep "bottle_at_filler" outputs/scenario_run/logic/hil_1.py
grep "_bottle_distance" outputs/scenario_run/logic/hil_1.py
DA FARE
-------
- Verificare che sensori leggano correttamente i nuovi range
- Eventualmente aggiungere thread separati come esempio (ora è single loop)
- Testare end-to-end con ICS-SimLab
================================================================================
FIX CRITICO: CONTRATTO ICS-SimLab logic() DEVE GIRARE FOREVER
================================================================================
Data: 2026-01-27
ROOT CAUSE IDENTIFICATA
-----------------------
ICS-SimLab chiama logic() UNA SOLA VOLTA in un thread e si aspetta che giri
per sempre. Il nostro codice generato invece ritornava subito → thread muore
→ nessun traffico.
Vedi: ICS-SimLab/src/components/plc.py linee 352-365:
logic_thread = Thread(target=logic.logic, args=(...), daemon=True)
logic_thread.start()
...
logic_thread.join() # ← Aspetta forever!
CONFRONTO CON ESEMPIO FUNZIONANTE (examples/water_tank/)
--------------------------------------------------------
Esempio funzionante PLC:
def logic(...):
time.sleep(2) # Aspetta sync
while True: # Loop infinito
# logica
time.sleep(0.1)
Nostro codice PRIMA:
def logic(...):
# logica
return # ← ERRORE: ritorna subito!
MODIFICHE EFFETTUATE
--------------------
File: tools/compile_ir.py
1. PLC logic ora genera:
- time.sleep(2) all'inizio per sync
- while True: loop infinito
- Logica dentro il loop con indent +4
- time.sleep(0.1) alla fine del loop
- _heartbeat() per log ogni 5 secondi
2. HIL logic ora genera:
- Inizializzazione diretta (non setdefault)
- time.sleep(3) per sync
- while True: loop infinito
- Fisica dentro il loop con indent +4
- time.sleep(0.1) alla fine del loop
3. _safe_callback migliorato:
- Cattura OSError e ConnectionException
- Ritorna bool per tracking
- 20 tentativi × 0.25s = 5s retry
STRUTTURA GENERATA ORA
----------------------
PLC:
def logic(input_registers, output_registers, state_update_callbacks):
time.sleep(2)
while True:
_heartbeat()
# logica con _write() e _get_float()
time.sleep(0.1)
HIL:
def logic(physical_values):
physical_values['key'] = initial_value
time.sleep(3)
while True:
# fisica
time.sleep(0.1)
VERIFICA
--------
# Rebuild scenario
.venv/bin/python3 build_scenario.py --out outputs/scenario_run --overwrite
# Verifica while True presente
grep "while True" outputs/scenario_run/logic/*.py
# Verifica time.sleep presente
grep "time.sleep" outputs/scenario_run/logic/*.py
# Esegui in ICS-SimLab
cd ~/projects/ICS-SimLab-main/curtin-ics-simlab
sudo docker-compose down -v
sudo rm -rf simulation
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
# Verifica nei log
sudo docker logs plc1 2>&1 | grep HEARTBEAT
sudo docker logs plc2 2>&1 | grep HEARTBEAT
================================================================================
MIGLIORAMENTI PLC E HIL: INIZIALIZZAZIONE + EXTERNAL WATCHER
================================================================================
Data: 2026-01-27
CONTESTO
--------
Confrontando con examples/water_tank/logic/plc1.py abbiamo notato che:
1. Il PLC esempio inizializza gli output e chiama i callback PRIMA del loop
2. Il PLC esempio traccia prev_output_valve per rilevare modifiche esterne (HMI)
3. Il nostro generatore non faceva né l'uno né l'altro
MODIFICHE EFFETTUATE
--------------------
A) PLC Generation (tools/compile_ir.py):
1. Explicit initialization phase PRIMA del while loop:
- Setta ogni output a 0
- Chiama callback per ogni output
- Aggiorna _prev_outputs per tracking
2. External-output watcher (_check_external_changes):
- Nuova funzione che rileva cambi esterni agli output (es. HMI)
- Chiamata all'inizio di ogni iterazione del loop
- Se output cambiato esternamente, chiama callback
3. _prev_outputs tracking:
- Dict globale che tiene traccia dei valori scritti dal PLC
- _write() aggiorna _prev_outputs quando scrive
- Evita double-callback: se il PLC ha scritto il valore, non serve callback
4. _collect_output_keys():
- Nuova funzione helper che estrae tutte le chiavi output dalle regole
- Usata per generare lista _output_keys per il watcher
B) HIL Generation (tools/compile_ir.py):
1. Bottle fill threshold:
- Bottiglia si riempie SOLO se bottle_fill < 200 (max)
- Evita overflow logico
C) Validator (services/validation/plc_callback_validation.py):
1. Riconosce pattern _write():
- Se file definisce funzione _write(), skip strict validation
- _write() gestisce internamente write + callback + tracking
PATTERN GENERATO ORA
--------------------
PLC (plc1.py, plc2.py):
def logic(input_registers, output_registers, state_update_callbacks):
global _prev_outputs
# --- Explicit initialization: set outputs and call callbacks ---
if 'tank_input_valve' in output_registers:
output_registers['tank_input_valve']['value'] = 0
_prev_outputs['tank_input_valve'] = 0
if 'tank_input_valve' in state_update_callbacks:
_safe_callback(state_update_callbacks['tank_input_valve'])
...
# Wait for other components to start
time.sleep(2)
_output_keys = ['tank_input_valve', 'tank_output_valve']
# Main loop - runs forever
while True:
_heartbeat()
# Check for external changes (e.g., HMI)
_check_external_changes(output_registers, state_update_callbacks, _output_keys)
# Control logic with _write()
...
time.sleep(0.1)
HIL (hil_1.py):
def logic(physical_values):
...
while True:
...
# Conservation: if bottle is at filler AND not full, water goes to bottle
if outlet_valve_on:
tank_level -= 6
if bottle_at_filler and bottle_fill < 200: # threshold
bottle_fill += 6
...
FUNZIONI HELPER GENERATE
------------------------
_write(out_regs, cbs, key, value):
- Scrive valore se diverso
- Aggiorna _prev_outputs[key] per tracking
- Chiama callback se presente
_check_external_changes(out_regs, cbs, keys):
- Per ogni key in keys:
- Se valore attuale != _prev_outputs[key]
- Valore cambiato esternamente (HMI)
- Chiama callback
- Aggiorna _prev_outputs
_safe_callback(cb, retries, delay):
- Retry logic per startup race conditions
- Cattura OSError e ConnectionException
VERIFICA
--------
# Rebuild
.venv/bin/python3 build_scenario.py --overwrite
# Verifica initialization
grep "Explicit initialization" outputs/scenario_run/logic/plc*.py
# Verifica external watcher
grep "_check_external_changes" outputs/scenario_run/logic/plc*.py
# Verifica bottle threshold
grep "bottle_fill < 200" outputs/scenario_run/logic/hil_1.py
================================================================================
FIX: AUTO-GENERAZIONE PLC MONITORS + SCALA THRESHOLD ASSOLUTI
================================================================================
Data: 2026-01-27
PROBLEMI IDENTIFICATI
---------------------
1) PLC monitors vuoti: i PLC non avevano outbound_connections ai sensori
e monitors era sempre []. I sensori erano attivi ma nessuno li interrogava.
2) Scala mismatch: HIL usa range interi (tank 0-1000, bottle 0-200) ma
i threshold PLC erano normalizzati (0.2, 0.8 su scala 0-1).
Risultato: 482 >= 0.8 sempre True -> logica sbagliata.
3) Modifiche manuali a configuration.json non persistono dopo rebuild.
SOLUZIONE IMPLEMENTATA
----------------------
A) Auto-generazione PLC monitors (tools/enrich_config.py):
- Nuovo tool che arricchisce configuration.json
- Per ogni PLC input register:
- Trova il HIL output corrispondente (es. water_tank_level -> water_tank_level_output)
- Trova il sensore che espone quel valore
- Aggiunge outbound_connection al sensore
- Aggiunge monitor entry per polling
- Per ogni PLC output register:
- Trova l'attuatore corrispondente (es. tank_input_valve -> tank_input_valve_input)
- Aggiunge outbound_connection all'attuatore
- Aggiunge controller entry
B) Scala threshold assoluti (models/ir_v1.py + tools/compile_ir.py):
- Aggiunto signal_max a HysteresisFillRule e ThresholdOutputRule
- make_ir_from_config.py: imposta signal_max=1000 per tank, signal_max=200 per bottle
- compile_ir.py: converte threshold normalizzati in assoluti:
- low=0.2, signal_max=1000 -> abs_low=200
- high=0.8, signal_max=1000 -> abs_high=800
- threshold=0.2, signal_max=200 -> abs_threshold=40
C) Pipeline aggiornata (build_scenario.py):
- Nuovo Step 0: chiama enrich_config.py
- Usa configuration_enriched.json per tutti gli step successivi
FILE MODIFICATI
---------------
- tools/enrich_config.py (NUOVO) - Arricchisce config con monitors
- models/ir_v1.py - Aggiunto signal_max ai rule
- tools/make_ir_from_config.py - Imposta signal_max per tank/bottle
- tools/compile_ir.py - Usa threshold assoluti
- build_scenario.py - Aggiunto Step 0 enrichment
VERIFICA
--------
# Rebuild scenario
.venv/bin/python3 build_scenario.py --overwrite
# Verifica monitors generati
grep -A10 '"monitors"' outputs/configuration_enriched.json
# Verifica threshold assoluti nel PLC
grep "lvl <=" outputs/scenario_run/logic/plc1.py
# Dovrebbe mostrare: if lvl <= 200.0 e elif lvl >= 800.0
grep "v <" outputs/scenario_run/logic/plc2.py
# Dovrebbe mostrare: if v < 40.0
# Esegui ICS-SimLab
cd ~/projects/ICS-SimLab-main/curtin-ics-simlab
sudo docker-compose down -v
sudo rm -rf simulation
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
================================================================================
FIX: VALORI INIZIALI RULE-AWARE (NO PIU' TUTTI ZERO)
================================================================================
Data: 2026-01-28
PROBLEMA OSSERVATO
------------------
- UI piatta: tank level ~482, bottle fill ~18 (non cambiano mai)
- Causa: init impostava TUTTI gli output a 0
- Con tank a 500 (mid-range tra low=200 e high=800), la logica hysteresis
non scrive nulla -> entrambe le valvole restano a 0 -> nessun flusso
- Sistema bloccato in steady state
SOLUZIONE
---------
Valori iniziali derivati dalle regole invece che tutti zero:
1) HysteresisFillRule:
- inlet_out = 0 (chiuso)
- outlet_out = 1 (APERTO) <- questo fa partire il drenaggio
- Tank scende -> raggiunge low=200 -> inlet si apre -> ciclo parte
2) ThresholdOutputRule:
- output_id = true_value (tipicamente 1)
- Attiva l'output inizialmente
FILE MODIFICATO
---------------
- tools/compile_ir.py
- Nuova funzione _compute_initial_values(rules) -> Dict[str, int]
- render_plc_rules() usa init_values invece di 0 fisso
- Commento nel codice generato spiega il perché
VERIFICA
--------
# Rebuild
.venv/bin/python3 build_scenario.py --overwrite
# Verifica init values nel PLC generato
grep -A3 "Explicit initialization" outputs/scenario_run/logic/plc1.py
# Deve mostrare: outlet = 1, inlet = 0
grep "tank_output_valve.*value.*=" outputs/scenario_run/logic/plc1.py
# Deve mostrare: output_registers['tank_output_valve']['value'] = 1
# Esegui e verifica che tank level cambi
cd ~/projects/ICS-SimLab-main/curtin-ics-simlab
sudo docker-compose down -v && sudo rm -rf simulation
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
# Dopo ~30 secondi, UI deve mostrare tank level che scende
================================================================================
FIX: HMI MONITOR ADDRESS DERIVAZIONE DA REGISTER MAP PLC
================================================================================
Data: 2026-01-28
PROBLEMA OSSERVATO
------------------
HMI logs mostrano ripetuti: "ERROR - Error: couldn't read values" per monitors
(water_tank_level, bottle_fill_level, bottle_at_filler).
Causa: i monitors HMI usavano value_type/address indovinati invece di derivarli
dalla mappa registri del PLC target. Es:
- HMI monitor bottle_fill_level: address=2 (SBAGLIATO)
- PLC2 register bottle_fill_level: address=1 (CORRETTO)
- HMI tentava di leggere holding_register@2 che non esiste -> errore Modbus
SOLUZIONE IMPLEMENTATA
----------------------
File modificato: tools/enrich_config.py
1) Nuova funzione helper find_register_mapping(device, id):
- Cerca in tutti i tipi registro (coil, discrete_input, holding_register, input_register)
- Ritorna (value_type, address, count) se trova il registro per id
- Ritorna None se non trovato
2) Nuova funzione enrich_hmi_connections(config):
- Per ogni HMI monitor che polla un PLC:
- Trova il PLC target tramite outbound_connection IP
- Cerca il registro nel PLC tramite find_register_mapping
- Aggiorna value_type, address, count per matchare il PLC
- Stampa "FIX:" quando corregge un valore
- Stampa "WARNING:" se registro non trovato (non indovina default)
- Stessa logica per controllers HMI
3) main() aggiornato:
- Chiama enrich_hmi_connections() dopo enrich_plc_connections()
- Summary include anche HMI monitors/controllers
ESEMPIO OUTPUT
--------------
$ python3 -m tools.enrich_config --config outputs/configuration.json \
--out outputs/configuration_enriched.json --overwrite
Enriching PLC connections...
Fixing HMI monitors/controllers...
FIX: hmi_1 monitor 'bottle_fill_level': holding_register@2 -> holding_register@1 (from plc2)
Summary:
plc1: 4 outbound_connections, 1 monitors, 2 controllers
plc2: 4 outbound_connections, 2 monitors, 2 controllers
hmi_1: 3 monitors, 1 controllers
VERIFICA
--------
# Rebuild scenario
python3 build_scenario.py --out outputs/scenario_run --overwrite
# Verifica che bottle_fill_level abbia address corretto
grep -A5 '"id": "bottle_fill_level"' outputs/configuration_enriched.json | grep address
# Deve mostrare: "address": 1 (non 2)
# Esegui ICS-SimLab
cd /home/stefano/projects/ICS-SimLab-main/curtin-ics-simlab
sudo docker-compose down -v && sudo rm -rf simulation
sudo ./start.sh /home/stefano/projects/ics-simlab-config-gen_claude/outputs/scenario_run
# Verifica che HMI non mostri più "couldn't read values"
sudo docker logs hmi_1 2>&1 | grep -i error
# UI deve mostrare valori che cambiano nel tempo
================================================================================
FIX: EXCLUDE_NONE=TRUE IN BUILD_CONFIG (IDENTITY NULL CRASH)
================================================================================
Data: 2026-01-30
PROBLEMA OSSERVATO
------------------
PLCs (plc1, plc2) crashavano all'avvio con:
TypeError: 'NoneType' object is not subscriptable
in /home/plc/plc.py linea 78.
Causa: configuration.json conteneva "identity": null per i PLC.
ICS-SimLab/src/components/plc.py fa:
if "identity" in configs:
identity.MajorMinorRevision = configs["identity"]["major_minor_revision"]
Il check "identity" in configs è True anche se il valore è None!
Quindi tenta configs[None]["major_minor_revision"] -> crash.
SOLUZIONE
---------
File modificato: tools/build_config.py
def config_to_dict(cfg: Config) -> Dict[str, Any]:
"""Convert Pydantic model to dict for JSON serialization.
Uses exclude_none=True to remove null values, which prevents
ICS-SimLab runtime errors like 'identity': None causing
TypeError when PLC code checks 'if "identity" in configs'.
"""
return cfg.model_dump(mode="json", exclude_none=True) # era exclude_none=False
VERIFICA
--------
python3 build_scenario.py --out outputs/scenario_run --overwrite
grep -n "identity" outputs/scenario_run/configuration.json
# Non deve trovare "identity" se non definita
docker logs plc1 2>&1 | head -20
# Non deve crashare con TypeError
================================================================================
WORKAROUND: HMI STARTUP RACE CONDITION (scripts/e2e.sh)
================================================================================
Data: 2026-01-30
PROBLEMA OSSERVATO
------------------
operator_hmi si avvia PRIMA che plc1 sia pronto su 192.168.100.21:502.
Risultato: Connection refused iniziale, poi HMI rimane in stato di errore
anche dopo che PLC diventa disponibile. Log ripetuti:
"ERROR - Error: couldn't read values"
NOTA: Questo è un problema architetturale di ICS-SimLab che non possiamo
modificare. La soluzione è un workaround host-side.
SOLUZIONE IMPLEMENTATA
----------------------
Nuovo file: scripts/e2e.sh
Script E2E che:
1. Verifica prerequisiti (scenario, simlab)
2. Stop container esistenti
3. Avvia simlab (docker compose up -d)
4. Polling: aspetta che plc1 e plc2 siano raggiungibili su porta 502
5. Restart operator_hmi dopo che PLCs sono pronti
6. Monitora log per N secondi
7. Salva log in outputs/run_<timestamp>/
8. Genera summary con conteggio errori
9. Stop simlab (a meno di --no-stop)
COMANDI
-------
# E2E test completo (start, test, stop)
./scripts/e2e.sh
# E2E test senza stop finale (per debug manuale)
./scripts/e2e.sh --no-stop
# Verifica risultati
ls outputs/run_*/
cat outputs/run_*/summary.txt
cat outputs/run_*/operator_hmi.log
OSSERVAZIONE AGGIUNTIVA
-----------------------
Anche DOPO restart HMI, i log mostrano "couldn't read values".
Questo indica un problema più profondo nel data flow, non solo
race condition. Possibili cause:
- HIL non sta producendo valori (physical_values non aggiornati)
- Sensori non ricevono dati da HIL
- Monitors PLC non leggono correttamente dai sensori
DA INVESTIGARE:
- docker logs physical_io_hil (solo "Starting HIL"?)
- water_tank_level_sensor register value (sempre 0?)
- Confrontare con example funzionante
PROSSIMO STEP
-------------
Investigare perché HIL non produce valori o perché sensori non li ricevono.
================================================================================
TOOL: MODBUS PROBE DIAGNOSTICO (tools/probe_modbus.py)
================================================================================
Data: 2026-01-30
SINTOMO OSSERVATO
-----------------
- PLC heartbeat mostra last_write_ok=False continuamente
- HMI riporta "couldn't read values" anche dopo restart
- Non è chiaro se il problema è:
- Connettività TCP
- Eccezione Modbus (illegal address/function)
- Mismatch tipo registro/indirizzo tra monitors e target
SOLUZIONE IMPLEMENTATA
----------------------
Nuovo file: tools/probe_modbus.py
Script diagnostico che:
1. Legge configuration.json
2. Estrae tutti i target dei monitor (HMI→PLC, PLC→Sensor)
3. Per ogni target:
- Verifica connettività TCP
- Esegue lettura Modbus con funzione corretta (coils/discrete/holding/input)
- Riporta OK + valore, oppure FAIL + dettaglio errore
4. Genera report con diagnosi automatica
INTEGRAZIONE IN E2E
-------------------
scripts/e2e.sh ora include:
- Step 4.5: Esegue probe via docker exec in operator_hmi
- Salva risultati in outputs/run_<ts>/probe.txt
- Mostra summary nel report finale
- Cattura log anche dei sensori
COMANDI
-------
# Test manuale del probe (da host con pymodbus)
python3 tools/probe_modbus.py --config outputs/scenario_run/configuration.json
# E2E completo con probe
./scripts/e2e.sh
# Verifica risultati probe
cat outputs/run_*/probe.txt
OUTPUT ESEMPIO
--------------
======================================================================
MODBUS PROBE REPORT
======================================================================
Total targets: 7
TCP reachable: 7/7
Modbus OK: 3/7
--- operator_hmi monitors ---
[OK] operator_hmi -> 192.168.100.21:502 input_register@200 (id=water_tank_level_reg) value=[0]
[FAIL] operator_hmi -> 192.168.100.22:502 input_register@210 (id=bottle_fill_level_reg) (TCP_OK) Modbus error: ...
--- plc1 monitors ---
[OK] plc1 -> 192.168.100.31:502 input_register@1 (id=water_tank_level_output) value=[500]
DIAGNOSI
--------
Se "TCP_FAIL": container non running, network isolation
Se "FAIL" con TCP_OK: indirizzo sbagliato, tipo registro sbagliato, device non serve
Se tutti OK con value=0: HIL non sta producendo valori
INTERPRETAZIONE
---------------
- value=[0] per sensori che dovrebbero avere dati → HIL non produce
- Modbus error "IllegalAddress" → indirizzo configurato sbagliato
- Modbus error "IllegalFunction" → tipo registro sbagliato (es. holding vs input)
================================================================================
FIX: PYMODBUS API COMPATIBILITY IN PROBE (tools/probe_modbus.py)
================================================================================
Data: 2026-01-30
PROBLEMA OSSERVATO
------------------
Il probe falliva con TypeError per TUTTI i target:
TypeError: ModbusClientMixin.read_input_registers() got an unexpected keyword argument 'slave'
TypeError: ... got an unexpected keyword argument 'unit'
La versione di pymodbus nel container ICS-SimLab è diversa da quella locale.
Diverse versioni di pymodbus hanno API diverse:
- pymodbus 2.x: usa `unit` come keyword argument
- pymodbus 3.x: usa `slave` come keyword argument
- alcune versioni: accettano solo keyword args (address=, count=)
- alcune versioni: accettano solo positional args
SOLUZIONE IMPLEMENTATA
----------------------
File modificato: tools/probe_modbus.py
Il probe ora prova 4 varianti API in sequenza:
```python
api_variants = [
lambda: func(address=target.address, count=target.count), # keyword, no slave
lambda: func(target.address, target.count), # positional only
lambda: func(address=..., count=..., slave=target.slave_id), # pymodbus 3.x
lambda: func(target.address, target.count, unit=target.slave_id), # pymodbus 2.x
]
for api_call in api_variants:
try:
result = api_call()
break
except TypeError:
continue
```
La prima variante che non solleva TypeError viene usata.
VERIFICA
--------
sudo ./scripts/e2e.sh
cat outputs/run_*/probe.txt
# Ora dovrebbe mostrare Modbus OK/FAIL con valori reali, non TypeError
NOTA IMPORTANTE
---------------
Questo fix rende il probe compatibile con qualsiasi versione di pymodbus.
La prima variante (keyword args senza slave) è la più probabile per ICS-SimLab.
STATUS ATTUALE
--------------
- TCP reachable: 8/8 ✓
- Probe API compatibility: FIXATO (vedi sotto)
- Prossimo: verificare che il probe mostri valori o errori Modbus reali
================================================================================
FIX: PYTHON LAMBDA CLOSURE BUG IN PROBE (tools/probe_modbus.py)
================================================================================
Data: 2026-01-30
PROBLEMA OSSERVATO
------------------
Nonostante il fix precedente con api_variants, il probe continuava a fallire
con TypeErrors catturati dal handler Exception esterno invece che dal
handler TypeError interno.
Output osservato:
Exception: TypeError: ModbusClientMixin.read_input_registers() got an unexpected keyword argument 'slave'
Il formato "Exception: TypeError:" indica che l'errore era catturato da:
except Exception as e:
error=f"Exception: {type(e).__name__}: {e}"
invece che dal loop interno:
except TypeError as e:
error=f"All API variants failed: {last_error}"
CAUSA ROOT
----------
Le lambda Python catturano variabili per riferimento, non per valore.
Anche se il codice sembrava corretto, qualche subtilità del closure
Python causava il mancato catching dell'eccezione.
SOLUZIONE IMPLEMENTATA
----------------------
File modificato: tools/probe_modbus.py
Sostituito l'approccio con lambda:
```python
api_variants = [
lambda: func(address=target.address, count=target.count), # NON FUNZIONAVA
...
]
for api_call in api_variants:
try:
result = api_call()
break
except TypeError:
continue
```
Con approccio esplicito if/else:
```python
addr = target.address
cnt = target.count
sid = target.slave_id
if result is None:
try:
result = func(address=addr, count=cnt)
except TypeError as e:
last_error = e
result = None
if result is None:
try:
result = func(addr, cnt)
except TypeError as e:
last_error = e
result = None
# ... etc per altre varianti
```
CORREZIONE SECONDARIA
---------------------
File modificato: scripts/e2e.sh
Bug: grep con pattern "Exception\|Traceback" produceva output multilinea
causando errore bash "integer expression expected".
Fix: Usato -E per extended regex e | head -1 per garantire singola linea:
```bash
PLC1_CRASHES=$(grep -Ec "Exception|Traceback" "$RUN_DIR/plc1.log" 2>/dev/null | head -1 || echo "0")
```
VERIFICA
--------
sudo ./scripts/e2e.sh
cat outputs/run_*/probe.txt | head -30
# Aspettativa: vedere "All API variants failed" oppure "Modbus OK" con valori
================================================================================
FEATURE: CONTROL PLAN - LOGICA HIL DICHIARATIVA
================================================================================
Data: 2026-01-30
OBIETTIVO
---------
Introdurre un nuovo artefatto "control_plan" che permetta di specificare
la fisica HIL in modo dichiarativo (loop, azioni, profili) senza codice
Python free-form. Il compilatore deterministico genera codice valido
per ICS-SimLab.
FILE CREATI
-----------
models/control_plan.py - Schema Pydantic per ControlPlan v0.1
- ControlPlanHIL con init, params, tasks
- Tasks: LoopTask, PlaybackTask
- Actions: SetAction, AddAction, IfAction
- Profiles: GaussianProfile, RampProfile, StepProfile
tools/safe_eval.py - Parser/evaluator espressioni sicuro
- Usa ast.parse per validare AST
- Whitelist di nodi e funzioni (min, max, clamp, etc.)
- Blocca import, attribute access, subscript
tools/compile_control_plan.py - Compiler: control_plan.json → HIL *.py
- CLI: --control-plan, --out, --config, --validate-only
- Genera codice con while True loop
- Threading automatico se >1 task
- Warmup sleep opzionale
tests/fixtures/control_plan_*.json - Test fixtures:
- control_plan_bottle_like.json (tank + bottle)
- control_plan_electrical_like.json (power grid)
- control_plan_ied_like.json (IED con protezioni)
tests/test_compile_control_plan.py - 24 test per schema, compiler, validation
FILE MODIFICATI
---------------
build_scenario.py - Aggiunto --control-plan argument
- Step 2c: compila control_plan se presente
- Auto-detect outputs/control_plan.json
SCHEMA CONTROL PLAN v0.1
------------------------
{
"version": "v0.1",
"hils": [{
"name": "...", // deve matchare hils[].name in config
"warmup_s": 3.0, // opzionale, delay prima di tasks
"init": {"var": value}, // valori iniziali physical_values
"params": {"const": value}, // costanti (read-only)
"tasks": [
{
"type": "loop",
"name": "physics",
"dt_s": 0.1,
"actions": [
{"set": ["var", "expr"]}, // var = expr
{"add": ["var", "expr"]}, // var += expr
{"if": "cond", "then": [...], "else": [...]}
]
},
{
"type": "playback",
"name": "noise",
"dt_s": 0.1,
"target": "var",
"profile": {"kind": "gaussian", "height": 50, "std": 2, "entries": 100}
}
]
}]
}
ESPRESSIONI SICURE
------------------
Allowlist funzioni: min, max, abs, int, float, bool, clamp
Allowlist operatori: +, -, *, /, //, %, **, <, <=, >, >=, ==, !=, and, or, not
Allowlist: ternary (x if cond else y)
Bloccato: import, attribute access (x.attr), subscript (x[i]), lambda
COMANDI
-------
# Compila control_plan
python3 -m tools.compile_control_plan \
--control-plan tests/fixtures/control_plan_bottle_like.json \
--out outputs/test_logic
# Solo validazione
python3 -m tools.compile_control_plan \
--control-plan tests/fixtures/control_plan_bottle_like.json \
--validate-only
# Build scenario con control plan
python3 build_scenario.py \
--out outputs/scenario_run \
--control-plan outputs/control_plan.json \
--overwrite
# Test
python3 -m pytest tests/test_compile_control_plan.py -v
VANTAGGI
--------
1. LLM genera spec strutturata, non Python
2. Compilazione deterministica e verificabile
3. Validazione pre-runtime (variabili undefined, espressioni invalide)
4. Sicuro: espressioni parse con AST whitelist
5. Flessibile: loop, profili (Gaussian, ramp, step), condizionali
6. Threading automatico per task paralleli
NOTA IMPORTANTE
---------------
control_plan è alternativo a process_spec e IR per HIL.
Se specificato, sovrascrive la logica HIL generata da IR.
PLC logic continua a usare IR (per ora).
VERIFICA
--------
python3 -m pytest tests/test_compile_control_plan.py -v
# ✅ 24 passed
================================================================================
E2E HARNESS: BOTTLE LINE CON CONTROL PLAN v0.1
================================================================================
Data: 2026-01-30
OBIETTIVO
---------
Creare un harness E2E riproducibile per testare la pipeline ControlPlan:
control_plan.json → HIL python generato → scenario build → validate_logic
Permette di validare l'intero flusso senza richiedere runtime Docker.
FILE CREATI
-----------
examples/control_plans/bottle_line_v0.1.json
Control plan con 2 HIL:
- water_hil: fisica serbatoio (level, input/output valves)
- filler_hil: fisica bottiglia (fill level, at_filler, conveyor)
Parametri realistici: tank 0-1000, bottle 0-200, conveyor 0-130
prompts/e2e_bottle.txt
Prompt per LLM che genera configuration.json compatibile:
- 2 PLC (plc1 tank, plc2 filler)
- 2 HIL (water_hil, filler_hil)
- Sensori e attuatori per ogni HIL
- Nomi HIL esatti per matchare control_plan
scripts/e2e_bottle_control_plan.sh
Script E2E che:
1. (Opzionale) genera config via LLM
2. Builda scenario con control plan
3. Valida logica generata
4. Stampa comando per ICS-SimLab (non esegue)
Opzioni: --skip-llm, --use-config PATH
tests/test_e2e_bottle_control_plan.py
17 test di integrazione:
- Control plan valido e compilabile
- Python generato sintatticamente corretto
- Funzione logic(physical_values) presente
- while True loop presente
- Warmup delay incluso
- Clamp function inclusa
COMANDI E2E
-----------
# Solo compilazione (senza LLM)
./scripts/e2e_bottle_control_plan.sh --skip-llm --use-config examples/water_tank/configuration.json
# Con LLM (richiede OPENAI_API_KEY)
./scripts/e2e_bottle_control_plan.sh
# Test di integrazione (no Docker)
python3 -m pytest tests/test_e2e_bottle_control_plan.py -v
ARCHITETTURA SCENARIO BOTTLE LINE
---------------------------------
PLC1 (192.168.100.21):
- Legge water_tank_level da sensore
- Controlla tank_input_valve, tank_output_valve
- Logica isteresi: low=200, high=800
PLC2 (192.168.100.22):
- Legge bottle_fill_level, bottle_at_filler
- Controlla conveyor_cmd, fill_valve
- Logica: riempi se at_filler e level < 180
water_hil (192.168.100.31):
- Fisica tank: inflow +18, outflow -12 per step
- Range 0-1000, init 500
filler_hil (192.168.100.32):
- Fisica bottle: fill +8 per step, max 200
- Conveyor: distance -= 5, reset a 130 quando < 0
- at_filler = 1 se distance <= 30
REPO HYGIENE
------------
- Pulito .gitignore (rimossi duplicati)
- .claude/settings.local.json già in .gitignore
- Solo appunti.txt (lowercase) e diario.md (come da CLAUDE.md)
VERIFICA
--------
python3 -m pytest tests/test_e2e_bottle_control_plan.py -v
# ✅ 17 passed
python3 -m tools.compile_control_plan \
--control-plan examples/control_plans/bottle_line_v0.1.json \
--validate-only
# ✅ Validation: OK
PROSSIMI STEP
-------------
- [ ] Test E2E con LLM (richiede OPENAI_API_KEY)
- [ ] Test runtime con ICS-SimLab
- [ ] Aggiungere altri esempi control_plan (ied, power_grid)
================================================================================
FIX: HIL INIT NON RILEVATA DA VALIDATOR (pv alias vs physical_values)
================================================================================
Data: 2026-02-02
PROBLEMA OSSERVATO
------------------
tools.validate_logic --check-hil-init falliva su HIL generati da compile_control_plan.py:
- water_hil.py: water_tank_level
- filler_hil.py: bottle_fill_level, bottle_at_filler, bottle_distance
ROOT CAUSE
----------
Il compilatore generava:
pv = physical_values # Alias
pv['water_tank_level'] = 500
Ma il validatore (services/validation/hil_init_validation.py) usa AST parser
che cerca SPECIFICAMENTE `physical_values[...]`, non alias come `pv[...]`.
Pattern riconosciuti dal validatore:
- physical_values["x"] = ...
- physical_values.setdefault("x", ...)
- physical_values.update({...})
NON riconosciuti:
- pv["x"] = ... (alias!)
SOLUZIONE IMPLEMENTATA
----------------------
File modificato: tools/compile_control_plan.py
1) compile_hil() ora accetta parametro opzionale config_physical_values: Set[str]
- Se fornito, inizializza TUTTI i keys dal config (non solo plan.init)
2) Genera `physical_values.setdefault('<key>', <default>)` PRIMA dell'alias:
- PRIMA: pv = physical_values; pv['key'] = value
- DOPO: physical_values.setdefault('key', value); pv = physical_values
3) Valori default:
- Se key in plan.init → usa valore da init
- Se key solo in config → usa 0 come default
4) compile_control_plan() passa config physical_values a compile_hil()
usando get_hil_physical_values_keys() già esistente
FILE TEST CREATI
----------------
tests/test_compile_control_plan_hil_init.py - 5 test regressione:
- test_compiled_hil_passes_init_validation
- test_compiled_hil_contains_setdefault_calls
- test_setdefault_before_alias
- test_init_value_preserved_from_plan
- test_config_only_keys_initialized_with_default
tests/fixtures/config_hil_bottle_like.json - Config fixture minimo per test
FILE MODIFICATI
---------------
tests/test_compile_control_plan.py - Aggiornato test esistente che cercava
pv['tank_level'] = 500 → ora cerca physical_values.setdefault('tank_level', 500)
CODICE GENERATO ORA
-------------------
def logic(physical_values):
"""..."""
# === Initialize physical values (validator-compatible) ===
physical_values.setdefault('bottle_at_filler', 1)
physical_values.setdefault('bottle_distance', 0)
physical_values.setdefault('bottle_fill_level', 0)
physical_values.setdefault('conveyor_cmd', 0)
physical_values.setdefault('fill_valve', 0)
pv = physical_values # Alias for generated code
# ... rest of logic
VERIFICA
--------
# Test di regressione (29 test)
python3 -m pytest tests/test_compile_control_plan.py tests/test_compile_control_plan_hil_init.py -v
# ✅ 29 passed
# Validazione su scenario compilato
python3 -m tools.compile_control_plan \
--control-plan examples/control_plans/bottle_line_v0.1.json \
--config tests/fixtures/config_hil_bottle_like.json \
--out /tmp/test_logic \
--overwrite
python3 -m tools.validate_logic \
--config tests/fixtures/config_hil_bottle_like.json \
--logic-dir /tmp/test_logic \
--check-hil-init
# ✅ OK: logica coerente con configuration.json
NOTA IMPORTANTE
---------------
Se config non fornito a compile_control_plan, inizializza solo keys da plan.init.
Per garantire validazione OK con --check-hil-init, passare sempre --config.
================================================================================
VALIDAZIONE NETWORK: DUPLICATE IP E SUBNET
================================================================================
Data: 2026-02-02
PROBLEMA OSSERVATO
------------------
ICS-SimLab docker-compose fallisce con:
"failed to set up container networking: Address already in use"
Root cause: più device con stesso IP sulla stessa docker_network.
Esempio trovato in outputs/scenario_bottle_cp/configuration.json:
- ui.network.ip = 192.168.100.10
- hmis[0].network.ip = 192.168.100.10
→ DUPLICATO!
SOLUZIONE IMPLEMENTATA
----------------------
1) Nuovo validatore in tools/semantic_validation.py:
- validate_network_config(config) -> List[SemanticError]
Controlla:
- IP duplicati entro stessa docker_network
- docker_network referenziato esiste in ip_networks[]
- IP è dentro la subnet dichiarata (usa ipaddress module)
2) Nuovo CLI: tools/check_networking.py
python3 -m tools.check_networking --config <path> [--strict] [--json]
Exit codes:
0 = OK
1 = Issues found
2 = Config file error
3) Integrazione in validate_all_semantics():
Network validation è P0, eseguita PRIMA degli altri check.
build_config.py già chiama validate_all_semantics(), quindi
la pipeline fallisce automaticamente su IP duplicati.
FILE CREATI
-----------
- tools/check_networking.py - CLI per validazione network
- tests/test_network_validation.py - 11 test per network validation
- tests/fixtures/config_duplicate_ip.json - IP duplicati (ui + hmi stesso IP)
- tests/fixtures/config_out_of_subnet_ip.json - IP fuori subnet
- tests/fixtures/config_unknown_docker_network.json - docker_network non esistente
FILE MODIFICATI
---------------
- tools/semantic_validation.py - Aggiunto validate_network_config()
VERIFICA
--------
# Test di regressione
python3 -m pytest tests/test_network_validation.py tests/test_semantic_validation_p0.py -v
# ✅ 43 passed
# CLI su config con problema
python3 -m tools.check_networking --config outputs/scenario_bottle_cp/configuration.json
# Output:
# NETWORK VALIDATION ISSUES (1):
# - network[ot_network]: Duplicate IP 192.168.100.10: ui (ui), hmi_supervision (hmi).
# CLI JSON output
python3 -m tools.check_networking --config outputs/scenario_bottle_cp/configuration.json --json
# {"config": "...", "issues": [...], "status": "error"}
PROSSIMI STEP
-------------
- [ ] Implementare auto-repair per assegnare IP liberi (Task D opzionale)
- [ ] Fixare il config scenario_bottle_cp assegnando IP diverso a HMI