169 lines
5.0 KiB
Python
169 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Verify that a scenario directory is complete and ready for Curtin ICS-SimLab.
|
|
|
|
Checks:
|
|
1. configuration.json exists
|
|
2. logic/ directory exists
|
|
3. All logic files referenced in config exist in logic/
|
|
4. (Optional) Run validate_logic checks
|
|
|
|
Usage:
|
|
python3 -m tools.verify_scenario --scenario outputs/scenario_run
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import List, Set, Tuple
|
|
|
|
|
|
def get_logic_files_from_config(config: dict) -> Tuple[Set[str], Set[str]]:
|
|
"""
|
|
Extract logic filenames referenced in configuration.
|
|
|
|
Returns: (plc_logic_files, hil_logic_files)
|
|
"""
|
|
plc_files: Set[str] = set()
|
|
hil_files: Set[str] = set()
|
|
|
|
for plc in config.get("plcs", []):
|
|
logic = plc.get("logic", "")
|
|
if logic:
|
|
plc_files.add(logic)
|
|
|
|
for hil in config.get("hils", []):
|
|
logic = hil.get("logic", "")
|
|
if logic:
|
|
hil_files.add(logic)
|
|
|
|
return plc_files, hil_files
|
|
|
|
|
|
def verify_scenario(scenario_dir: Path) -> Tuple[bool, List[str]]:
|
|
"""
|
|
Verify scenario directory is complete.
|
|
|
|
Returns: (success: bool, errors: List[str])
|
|
"""
|
|
errors: List[str] = []
|
|
|
|
# Check configuration.json exists
|
|
config_path = scenario_dir / "configuration.json"
|
|
if not config_path.exists():
|
|
errors.append(f"Missing: {config_path}")
|
|
return False, errors
|
|
|
|
# Load config
|
|
try:
|
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
except Exception as e:
|
|
errors.append(f"Invalid JSON in {config_path}: {e}")
|
|
return False, errors
|
|
|
|
# Check logic/ directory exists
|
|
logic_dir = scenario_dir / "logic"
|
|
if not logic_dir.exists():
|
|
errors.append(f"Missing directory: {logic_dir}")
|
|
return False, errors
|
|
|
|
# Check all referenced logic files exist
|
|
plc_files, hil_files = get_logic_files_from_config(config)
|
|
all_files = plc_files | hil_files
|
|
|
|
for fname in sorted(all_files):
|
|
fpath = logic_dir / fname
|
|
if not fpath.exists():
|
|
errors.append(f"Missing logic file: {fpath} (referenced in config)")
|
|
|
|
# Check for orphan logic files (warning only)
|
|
existing_files = {f.name for f in logic_dir.glob("*.py")}
|
|
orphans = existing_files - all_files
|
|
if orphans:
|
|
# Not an error, just informational
|
|
pass
|
|
|
|
success = len(errors) == 0
|
|
return success, errors
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Verify scenario directory is complete for ICS-SimLab"
|
|
)
|
|
parser.add_argument(
|
|
"--scenario",
|
|
required=True,
|
|
help="Path to scenario directory (e.g., outputs/scenario_run)",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose",
|
|
"-v",
|
|
action="store_true",
|
|
help="Show detailed information",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
scenario_dir = Path(args.scenario)
|
|
|
|
if not scenario_dir.exists():
|
|
raise SystemExit(f"ERROR: Scenario directory not found: {scenario_dir}")
|
|
|
|
print(f"Verifying scenario: {scenario_dir}")
|
|
print()
|
|
|
|
success, errors = verify_scenario(scenario_dir)
|
|
|
|
if args.verbose or success:
|
|
# Show contents
|
|
config_path = scenario_dir / "configuration.json"
|
|
logic_dir = scenario_dir / "logic"
|
|
|
|
if config_path.exists():
|
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
plc_files, hil_files = get_logic_files_from_config(config)
|
|
|
|
print("Configuration:")
|
|
print(f" PLCs: {len(config.get('plcs', []))}")
|
|
print(f" HILs: {len(config.get('hils', []))}")
|
|
print(f" Sensors: {len(config.get('sensors', []))}")
|
|
print(f" Actuators: {len(config.get('actuators', []))}")
|
|
print()
|
|
|
|
print("Logic files referenced:")
|
|
for f in sorted(plc_files):
|
|
status = "OK" if (logic_dir / f).exists() else "MISSING"
|
|
print(f" [PLC] {f}: {status}")
|
|
for f in sorted(hil_files):
|
|
status = "OK" if (logic_dir / f).exists() else "MISSING"
|
|
print(f" [HIL] {f}: {status}")
|
|
print()
|
|
|
|
# Show orphans
|
|
if logic_dir.exists():
|
|
existing = {f.name for f in logic_dir.glob("*.py")}
|
|
orphans = existing - (plc_files | hil_files)
|
|
if orphans:
|
|
print("Orphan files (not referenced in config):")
|
|
for f in sorted(orphans):
|
|
print(f" {f}")
|
|
print()
|
|
|
|
if errors:
|
|
print(f"VERIFICATION FAILED: {len(errors)} error(s)")
|
|
for err in errors:
|
|
print(f" - {err}")
|
|
raise SystemExit(1)
|
|
else:
|
|
print("VERIFICATION PASSED: Scenario is complete")
|
|
print()
|
|
print("To run with ICS-SimLab:")
|
|
print(f" cd ~/projects/ICS-SimLab-main/curtin-ics-simlab")
|
|
print(f" sudo ./start.sh {scenario_dir.absolute()}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|