From b98c2830e4af0e86d30a3aa4180fb3bd9d9e84c7 Mon Sep 17 00:00:00 2001 From: RinZ27 <222222878+RinZ27@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:39:40 +0700 Subject: [PATCH] fix(engine): improve drone stability and fix project CI validation --- eosim/__main__.py | 3 +- eosim/artifacts/__init__.py | 2 +- eosim/artifacts/manager.py | 11 +- eosim/cli/__init__.py | 2 +- eosim/cli/main.py | 87 +++++++------- eosim/core/cluster.py | 14 +-- eosim/core/domains.py | 18 +-- eosim/core/host.py | 10 +- eosim/core/jobs.py | 10 +- eosim/core/modeling.py | 20 ++-- eosim/core/platform.py | 11 +- eosim/core/registry.py | 27 ++--- eosim/core/schema.py | 22 ++-- eosim/engine/__init__.py | 2 +- eosim/engine/backend.py | 26 ++--- eosim/engine/native/__init__.py | 27 +++-- eosim/engine/native/cpu/__init__.py | 9 +- eosim/engine/native/memory/__init__.py | 11 +- eosim/engine/native/peripherals/__init__.py | 8 +- .../peripherals/actuators_aerodynamics.py | 1 - .../native/peripherals/actuators_gaming.py | 1 - .../peripherals/actuators_physiology.py | 1 - .../native/peripherals/actuators_weather.py | 1 - eosim/engine/native/peripherals/buses.py | 20 ++-- .../native/peripherals/sensors_finance.py | 1 - .../native/peripherals/sensors_gaming.py | 2 - .../native/peripherals/sensors_physiology.py | 1 - .../native/peripherals/sensors_weather.py | 1 - eosim/engine/native/peripherals/wireless.py | 1 - eosim/engine/native/simulators/__init__.py | 24 ++-- .../engine/native/simulators/aerodynamics.py | 16 ++- eosim/engine/native/simulators/aircraft.py | 110 +----------------- eosim/engine/native/simulators/camera.py | 4 +- eosim/engine/native/simulators/drone.py | 6 +- eosim/engine/native/simulators/energy.py | 85 ++------------ eosim/engine/native/simulators/finance.py | 8 +- eosim/engine/native/simulators/gaming.py | 7 +- eosim/engine/native/simulators/industrial.py | 106 ++--------------- eosim/engine/native/simulators/iot.py | 9 +- eosim/engine/native/simulators/media.py | 2 +- eosim/engine/native/simulators/medical.py | 11 +- eosim/engine/native/simulators/physiology.py | 15 ++- eosim/engine/native/simulators/robot.py | 85 +------------- eosim/engine/native/simulators/satellite.py | 95 ++------------- eosim/engine/native/simulators/speaker.py | 5 +- eosim/engine/native/simulators/vehicle.py | 10 +- eosim/engine/native/simulators/wearable.py | 8 +- eosim/engine/native/simulators/weather.py | 10 +- eosim/engine/peripherals.py | 6 +- eosim/engine/qemu/elf_loader.py | 10 +- eosim/engine/qemu/gdb_client.py | 9 +- eosim/engine/qemu/qmp_client.py | 6 +- eosim/engine/qemu/state_bridge.py | 2 +- eosim/gui/product_templates.py | 8 +- eosim/gui/renderers/__init__.py | 5 +- eosim/gui/renderers/aerodynamics.py | 3 +- eosim/gui/renderers/consumer.py | 1 + eosim/gui/renderers/finance.py | 2 +- eosim/gui/renderers/generic.py | 1 + eosim/gui/renderers/physiology.py | 2 +- eosim/gui/renderers/weather.py | 3 +- eosim/gui/simulator_app.py | 4 +- eosim/gui/tk_app.py | 8 +- eosim/gui/widgets/build_panel.py | 11 +- eosim/gui/widgets/peripheral_panel.py | 1 - eosim/gui/widgets/viewer_3d.py | 2 +- eosim/integrations/__init__.py | 16 ++- eosim/integrations/ecosystem.py | 13 +-- eosim/integrations/eos_runner.py | 16 +-- eosim/integrations/gazebo.py | 6 +- eosim/integrations/hil_session.py | 4 +- eosim/integrations/openfoam.py | 22 ++-- eosim/integrations/openocd.py | 5 +- eosim/integrations/serial_bridge.py | 6 +- eosim/integrations/xplane.py | 12 +- eosim/tests/__init__.py | 4 +- eosim/tests/runner.py | 13 ++- eosim/tests/scenarios.py | 14 +-- tests/__init__.py | 2 +- tests/conftest.py | 6 +- tests/integration/__init__.py | 2 +- tests/integration/test_cli_commands.py | 1 + tests/integration/test_platform_pipeline.py | 6 +- tests/integration/test_scenario_runner.py | 3 +- tests/unit/__init__.py | 2 +- tests/unit/test_core.py | 18 +-- tests/unit/test_gui.py | 34 +++--- tests/unit/test_integrations.py | 16 +-- tests/unit/test_new_domains.py | 29 +++-- tests/unit/test_new_gui.py | 28 ++--- 90 files changed, 477 insertions(+), 851 deletions(-) diff --git a/eosim/__main__.py b/eosim/__main__.py index a1d6e24..508527f 100644 --- a/eosim/__main__.py +++ b/eosim/__main__.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: MIT """EoSim CLI entry point.""" from eosim.cli.main import cli + if __name__ == "__main__": - cli() \ No newline at end of file + cli() diff --git a/eosim/artifacts/__init__.py b/eosim/artifacts/__init__.py index 95bdbe7..9d40381 100644 --- a/eosim/artifacts/__init__.py +++ b/eosim/artifacts/__init__.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT """Artifacts package.""" -from eosim.artifacts.manager import collect_artifacts, generate_junit \ No newline at end of file +from eosim.artifacts.manager import collect_artifacts, generate_junit diff --git a/eosim/artifacts/manager.py b/eosim/artifacts/manager.py index 0af0583..881da7c 100644 --- a/eosim/artifacts/manager.py +++ b/eosim/artifacts/manager.py @@ -1,11 +1,14 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Artifact collection and export.""" -import os, json, shutil +import json +import os +import shutil from datetime import datetime, timezone -from pathlib import Path + from eosim.engine.backend import SimResult + def collect_artifacts(result: SimResult, output_dir: str = "out/artifacts") -> dict: os.makedirs(output_dir, exist_ok=True) manifest = { @@ -36,11 +39,11 @@ def generate_junit(results: list, output: str = "out/reports/junit.xml") -> str: for r in results: name = r.get("platform", "unknown") t = r.get("duration_s", 0) - lines.append(' ' % (name, t)) + lines.append(f' ') if not r.get("success", False): lines.append(' Simulation failed') lines.append(' ') lines.append('') with open(output, "w") as f: f.write("\n".join(lines)) - return output \ No newline at end of file + return output diff --git a/eosim/cli/__init__.py b/eosim/cli/__init__.py index 94e991b..30033c7 100644 --- a/eosim/cli/__init__.py +++ b/eosim/cli/__init__.py @@ -1,2 +1,2 @@ # SPDX-License-Identifier: MIT -"""CLI package.""" \ No newline at end of file +"""CLI package.""" diff --git a/eosim/cli/main.py b/eosim/cli/main.py index 8099c99..01b9281 100644 --- a/eosim/cli/main.py +++ b/eosim/cli/main.py @@ -1,15 +1,16 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """EoSim CLI - primary entry point.""" -import click -import sys -import os import json -import yaml -import subprocess +import os import shutil +import subprocess +import sys from pathlib import Path +import click +import yaml + EOSIM_ROOT = Path(__file__).parent.parent.parent PLATFORMS_DIR = EOSIM_ROOT / "platforms" @@ -90,8 +91,7 @@ def list_platforms(arch, vendor, platform_class, engine, domain, group_by_field, elif fmt == "csv": click.echo("name,arch,engine,vendor,class,soc,domain") for p in platforms: - click.echo("%s,%s,%s,%s,%s,%s,%s" % ( - p.name, p.arch, p.engine, p.vendor, p.platform_class, p.soc, p.domain)) + click.echo(f"{p.name},{p.arch},{p.engine},{p.vendor},{p.platform_class},{p.soc},{p.domain}") else: click.echo("Available platforms (%d):\n" % len(platforms)) _print_platforms(platforms) @@ -116,7 +116,7 @@ def search(query): reg = _load_registry() results = reg.search(query) if not results: - click.echo("No platforms matching: %s" % query) + click.echo(f"No platforms matching: {query}") return click.echo("Search results for '%s' (%d matches):\n" % (query, len(results))) for p in results: @@ -132,7 +132,7 @@ def stats(): click.echo("EoSim Platform Statistics (%d platforms)\n" % reg.count()) for category, counts in st.items(): display_name = {"platform_class": "class"}.get(category, category) - click.echo(" %s:" % display_name) + click.echo(f" {display_name}:") for value, count in counts.items(): click.echo(" %-20s %d" % (value, count)) click.echo() @@ -163,7 +163,7 @@ def run(platform, headless, timeout, log_dir): engine = p.get("engine", "renode") arch = p.get("arch", "unknown") - click.echo("EoSim: launching %s (%s) via %s" % (platform, arch, engine)) + click.echo(f"EoSim: launching {platform} ({arch}) via {engine}") os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, platform + ".log") @@ -223,10 +223,10 @@ def _run_qemu(p, platform, headless, timeout, log_file): } qemu = shutil.which(qemu_map.get(arch, "qemu-system-" + arch)) if not qemu: - click.echo("QEMU not found for %s — simulation skipped" % arch) - click.echo("Install: sudo apt install qemu-system-%s" % arch) + click.echo(f"QEMU not found for {arch} — simulation skipped") + click.echo(f"Install: sudo apt install qemu-system-{arch}") with open(log_file, "w") as f: - f.write("QEMU not available for %s\nPASSED (dry run)\n" % arch) + f.write(f"QEMU not available for {arch}\nPASSED (dry run)\n") click.echo("PASSED (dry run)") return machine = p.get("qemu", {}).get("machine", "virt") @@ -249,7 +249,7 @@ def _run_eosim(p, platform, headless, timeout, log_file): result = vm.run(max_cycles=10000, timeout_s=float(timeout)) with open(log_file, "w") as f: f.write("=== EoSim Native Log ===\n") - f.write("Platform: %s\nArch: %s\n\n" % (platform, arch)) + f.write(f"Platform: {platform}\nArch: {arch}\n\n") f.write(result.get("boot_log", "")) click.echo("Log: " + log_file) if result.get("success"): @@ -279,7 +279,7 @@ def test(platform, timeout, junit): passed = 0 for c in checks: ctype = c.get("type", "") - click.echo(" [CHECK] %s: %s" % (ctype, c.get("value", c.get("seconds", "")))) + click.echo(" [CHECK] {}: {}".format(ctype, c.get("value", c.get("seconds", "")))) passed += 1 click.echo("Result: %d/%d passed" % (passed, len(checks))) @@ -303,7 +303,7 @@ def validate(platform_config, validate_all): errors = validate_platform(p) if errors: failed += 1 - click.echo("FAILED: %s" % sub.name) + click.echo(f"FAILED: {sub.name}") for e in errors: click.echo(" ERROR: " + e) else: @@ -345,7 +345,7 @@ def simulate(platform, duration, headless, nested_install): engine = p.get("engine", "renode") arch = p.get("arch", "unknown") - click.echo("EoSim: simulating %s (%s) via %s" % (platform, arch, engine)) + click.echo(f"EoSim: simulating {platform} ({arch}) via {engine}") log_dir = "out/logs" os.makedirs(log_dir, exist_ok=True) @@ -362,7 +362,7 @@ def simulate(platform, duration, headless, nested_install): sys.exit(1) if nested_install: - click.echo("Nested install test: simulated for %s" % platform) + click.echo(f"Nested install test: simulated for {platform}") @cli.command("list-platforms") @@ -461,17 +461,17 @@ def domain_info(name): from eosim.core.domains import get_domain d = get_domain(name) if not d: - click.echo("Unknown domain: %s" % name, err=True) + click.echo(f"Unknown domain: {name}", err=True) sys.exit(1) - click.echo("Domain: %s" % d.display_name) - click.echo("Description: %s" % d.description) + click.echo(f"Domain: {d.display_name}") + click.echo(f"Description: {d.description}") if d.safety_levels: - click.echo("Safety Levels: %s" % ", ".join(d.safety_levels)) - click.echo("Standards: %s" % ", ".join(d.standards)) - click.echo("Typical Arches: %s" % ", ".join(d.typical_arches)) - click.echo("Typical Classes: %s" % ", ".join(d.typical_classes)) + click.echo("Safety Levels: {}".format(", ".join(d.safety_levels))) + click.echo("Standards: {}".format(", ".join(d.standards))) + click.echo("Typical Arches: {}".format(", ".join(d.typical_arches))) + click.echo("Typical Classes: {}".format(", ".join(d.typical_classes))) if d.test_scenarios: - click.echo("Test Scenarios: %s" % ", ".join(d.test_scenarios)) + click.echo("Test Scenarios: {}".format(", ".join(d.test_scenarios))) # --- Modeling subcommands --- @@ -499,12 +499,12 @@ def modeling_info(name): from eosim.core.modeling import get_modeling m = get_modeling(name) if not m: - click.echo("Unknown modeling method: %s" % name, err=True) + click.echo(f"Unknown modeling method: {name}", err=True) sys.exit(1) - click.echo("Method: %s" % m.display_name) - click.echo("Description: %s" % m.description) - click.echo("Supported Engines: %s" % ", ".join(m.engine_support)) - click.echo("Use Cases: %s" % ", ".join(m.use_cases)) + click.echo(f"Method: {m.display_name}") + click.echo(f"Description: {m.description}") + click.echo("Supported Engines: {}".format(", ".join(m.engine_support))) + click.echo("Use Cases: {}".format(", ".join(m.use_cases))) if m.parameters: click.echo("Parameters:") for pname, ptype in m.parameters.items(): @@ -534,7 +534,7 @@ def eos_find(): @click.option("--source", default=None, help="EoS source directory") def eos_build(source): """Build EoS from source.""" - from eosim.integrations.eos_runner import find_eos_source, build_eos + from eosim.integrations.eos_runner import build_eos, find_eos_source src = source or find_eos_source() if not src: click.echo("EoS source not found", err=True) @@ -566,7 +566,7 @@ def eos_test(source, verbose): if verbose: for r in suite.results: if r.output and not r.passed: - click.echo("\n--- %s ---" % r.name) + click.echo(f"\n--- {r.name} ---") click.echo(r.output[-300:]) if suite.failed > 0: sys.exit(1) @@ -602,7 +602,7 @@ def eos_test_suite(source): @click.option("--simulate/--no-simulate", default=True, help="Run simulations") def ecosystem(workspace, simulate): """Test ALL EoS repos — build, test, simulate, validate.""" - from eosim.integrations.ecosystem import run_ecosystem_tests, find_repos + from eosim.integrations.ecosystem import find_repos, run_ecosystem_tests click.echo("EoSim Ecosystem Validation") click.echo("") repos = find_repos(workspace) @@ -621,6 +621,7 @@ def ecosystem(workspace, simulate): def gui(): """Launch the EoSim simulation UI.""" import tkinter as tk + from eosim.gui.tk_app import TkSimulatorApp root = tk.Tk() @@ -653,7 +654,7 @@ def hil_detect(): openocd = OpenOCDManager.find_openocd() if openocd: - click.echo("OpenOCD: %s" % openocd) + click.echo(f"OpenOCD: {openocd}") else: click.echo("OpenOCD: NOT FOUND (install from https://openocd.org/)") @@ -685,7 +686,7 @@ def hil_connect(adapter, target, serial_port, baudrate, gdb_port): """Connect to a real development board via OpenOCD.""" from eosim.integrations.hil_session import HILSession - click.echo("EoSim HIL — Connecting to %s via %s" % (target, adapter)) + click.echo(f"EoSim HIL — Connecting to {target} via {adapter}") session = HILSession() try: session.start( @@ -710,7 +711,7 @@ def hil_connect(adapter, target, serial_port, baudrate, gdb_port): except KeyboardInterrupt: pass except Exception as e: - click.echo("Connection failed: %s" % e, err=True) + click.echo(f"Connection failed: {e}", err=True) sys.exit(1) finally: session.stop() @@ -725,7 +726,7 @@ def hil_flash(firmware, adapter, target): """Flash firmware to a real target via OpenOCD.""" from eosim.integrations.openocd import OpenOCDManager - click.echo("EoSim HIL — Flashing %s to %s via %s" % (firmware, target, adapter)) + click.echo(f"EoSim HIL — Flashing {firmware} to {target} via {adapter}") mgr = OpenOCDManager() try: ok = mgr.flash(firmware) @@ -735,7 +736,7 @@ def hil_flash(firmware, adapter, target): click.echo("Flash: FAILED") sys.exit(1) except Exception as e: - click.echo("Flash error: %s" % e, err=True) + click.echo(f"Flash error: {e}", err=True) sys.exit(1) @@ -747,7 +748,7 @@ def hil_monitor(adapter, target, gdb_port): """Live register/memory monitoring (text mode).""" from eosim.integrations.hil_session import HILSession - click.echo("EoSim HIL Monitor — %s via %s (Ctrl+C to quit)\n" % (target, adapter)) + click.echo(f"EoSim HIL Monitor — {target} via {adapter} (Ctrl+C to quit)\n") session = HILSession() try: session.start(adapter=adapter, target=target, gdb_port=gdb_port) @@ -757,7 +758,7 @@ def hil_monitor(adapter, target, gdb_port): regs = session.read_registers() if regs: click.echo("\033[2J\033[H") - click.echo("=== %s Registers ===" % target) + click.echo(f"=== {target} Registers ===") for name, val in sorted(regs.items()): click.echo(" %-6s 0x%08X" % (name, val)) time.sleep(0.5) @@ -778,7 +779,7 @@ def bridge(): @bridge.command("status") def bridge_status(): """Show status of all external tool bridges.""" - from eosim.engine.backend import XPlaneEngine, GazeboEngine, OpenFOAMEngine + from eosim.engine.backend import GazeboEngine, OpenFOAMEngine, XPlaneEngine click.echo("EoSim Bridge Status\n") click.echo(" %-15s %s" % ("X-Plane", "available" if XPlaneEngine.available() else "not connected")) click.echo(" %-15s %s" % ("Gazebo", "available" if GazeboEngine.available() else "not installed")) @@ -847,7 +848,7 @@ def bridge_openfoam(): def openfoam_run(case_dir, solver): """Run an OpenFOAM simulation.""" from eosim.integrations.openfoam import OpenFOAMRunner - click.echo("Running OpenFOAM solver '%s' on case: %s" % (solver, case_dir)) + click.echo(f"Running OpenFOAM solver '{solver}' on case: {case_dir}") runner = OpenFOAMRunner(case_dir=case_dir) runner.set_solver(solver) result = runner.run() diff --git a/eosim/core/cluster.py b/eosim/core/cluster.py index 3445ad3..7e6b7bc 100644 --- a/eosim/core/cluster.py +++ b/eosim/core/cluster.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT """Cluster definitions for multi-node simulations.""" -import yaml from dataclasses import dataclass, field -from typing import List, Dict, Optional + +import yaml @dataclass @@ -14,8 +14,8 @@ class ClusterNode: @dataclass class Cluster: name: str = "" - nodes: List[ClusterNode] = field(default_factory=list) - links: List = field(default_factory=list) + nodes: list[ClusterNode] = field(default_factory=list) + links: list = field(default_factory=list) @classmethod def from_yaml(cls, path: str) -> "Cluster": @@ -33,13 +33,13 @@ def from_yaml(cls, path: str) -> "Cluster": links=data.get("links", []), ) - def validate(self, known_platforms: Dict) -> List[str]: + def validate(self, known_platforms: dict) -> list[str]: errors = [] seen_names = set() for node in self.nodes: if node.name in seen_names: - errors.append("duplicate node name: %s" % node.name) + errors.append(f"duplicate node name: {node.name}") seen_names.add(node.name) if node.platform not in known_platforms: - errors.append("unknown platform: %s" % node.platform) + errors.append(f"unknown platform: {node.platform}") return errors diff --git a/eosim/core/domains.py b/eosim/core/domains.py index 94f676e..18bede4 100644 --- a/eosim/core/domains.py +++ b/eosim/core/domains.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT """Domain profiles catalog for simulation contexts.""" from dataclasses import dataclass, field -from typing import List, Dict, Optional +from typing import Optional @dataclass @@ -9,14 +9,14 @@ class DomainProfile: name: str = "" display_name: str = "" description: str = "" - standards: List[str] = field(default_factory=list) - safety_levels: List[str] = field(default_factory=list) - typical_arches: List[str] = field(default_factory=list) - typical_classes: List[str] = field(default_factory=list) - test_scenarios: List[str] = field(default_factory=list) + standards: list[str] = field(default_factory=list) + safety_levels: list[str] = field(default_factory=list) + typical_arches: list[str] = field(default_factory=list) + typical_classes: list[str] = field(default_factory=list) + test_scenarios: list[str] = field(default_factory=list) -DOMAIN_CATALOG: Dict[str, DomainProfile] = { +DOMAIN_CATALOG: dict[str, DomainProfile] = { "automotive": DomainProfile( name="automotive", display_name="Automotive / Transportation", @@ -173,7 +173,7 @@ class DomainProfile: } -def list_domains() -> List[str]: +def list_domains() -> list[str]: return list(DOMAIN_CATALOG.keys()) @@ -181,7 +181,7 @@ def get_domain(name: str) -> Optional[DomainProfile]: return DOMAIN_CATALOG.get(name) -def suggest_platforms(domain: str, registry) -> List: +def suggest_platforms(domain: str, registry) -> list: profile = get_domain(domain) if profile is None: return [] diff --git a/eosim/core/host.py b/eosim/core/host.py index dde7292..5b17d55 100644 --- a/eosim/core/host.py +++ b/eosim/core/host.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: MIT """Host environment detection and binary resolution.""" import os -import sys -import shutil import platform as _platform -from dataclasses import dataclass, field -from typing import Optional, Dict +import shutil +import sys +from dataclasses import dataclass +from typing import Optional @dataclass @@ -29,7 +29,7 @@ def detect(cls) -> "HostEnvironment": python_version="%d.%d.%d" % sys.version_info[:3], ) - def platform_info(self) -> Dict[str, str]: + def platform_info(self) -> dict[str, str]: return { "os": self.os_name, "arch": self.arch, diff --git a/eosim/core/jobs.py b/eosim/core/jobs.py index fb1caa6..e16e9d6 100644 --- a/eosim/core/jobs.py +++ b/eosim/core/jobs.py @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MIT """Job queue for simulation runs.""" -import os import json +import os import uuid -from dataclasses import dataclass, asdict -from typing import Optional, List +from dataclasses import asdict, dataclass +from typing import Optional @dataclass @@ -21,7 +21,7 @@ def __init__(self, storage_dir: str): os.makedirs(storage_dir, exist_ok=True) def _job_path(self, job_id: str) -> str: - return os.path.join(self._storage_dir, "%s.json" % job_id) + return os.path.join(self._storage_dir, f"{job_id}.json") def submit(self, platform: str, engine: str = "eosim") -> Job: job = Job( @@ -42,7 +42,7 @@ def get(self, job_id: str) -> Optional[Job]: data = json.load(f) return Job(**data) - def list_jobs(self) -> List[Job]: + def list_jobs(self) -> list[Job]: jobs = [] for fname in os.listdir(self._storage_dir): if fname.endswith(".json"): diff --git a/eosim/core/modeling.py b/eosim/core/modeling.py index 7435fd5..4cdcb5c 100644 --- a/eosim/core/modeling.py +++ b/eosim/core/modeling.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT """Modeling method catalog for simulation approaches.""" from dataclasses import dataclass, field -from typing import List, Dict, Optional +from typing import Optional @dataclass @@ -9,12 +9,12 @@ class ModelingMethod: name: str = "" display_name: str = "" description: str = "" - engine_support: List[str] = field(default_factory=list) - use_cases: List[str] = field(default_factory=list) - parameters: Dict = field(default_factory=dict) + engine_support: list[str] = field(default_factory=list) + use_cases: list[str] = field(default_factory=list) + parameters: dict = field(default_factory=dict) -MODELING_CATALOG: Dict[str, ModelingMethod] = { +MODELING_CATALOG: dict[str, ModelingMethod] = { "deterministic": ModelingMethod( name="deterministic", display_name="Deterministic Simulation", @@ -98,7 +98,7 @@ class ModelingMethod: } -def list_modeling_methods() -> List[str]: +def list_modeling_methods() -> list[str]: return list(MODELING_CATALOG.keys()) @@ -106,15 +106,15 @@ def get_modeling(name: str) -> Optional[ModelingMethod]: return MODELING_CATALOG.get(name) -def validate_modeling_for_engine(method: str, engine: str) -> List[str]: +def validate_modeling_for_engine(method: str, engine: str) -> list[str]: warnings = [] m = get_modeling(method) if m is None: - warnings.append("Unknown modeling method: %s" % method) + warnings.append(f"Unknown modeling method: {method}") return warnings if engine not in m.engine_support: warnings.append( - "Modeling method '%s' is not supported by engine '%s'. " - "Supported engines: %s" % (method, engine, ", ".join(m.engine_support)) + "Modeling method '{}' is not supported by engine '{}'. " + "Supported engines: {}".format(method, engine, ", ".join(m.engine_support)) ) return warnings diff --git a/eosim/core/platform.py b/eosim/core/platform.py index 72fd1e3..0741fde 100644 --- a/eosim/core/platform.py +++ b/eosim/core/platform.py @@ -1,9 +1,10 @@ # SPDX-License-Identifier: MIT """Platform dataclasses and discovery.""" import os -import yaml from dataclasses import dataclass, field -from typing import Optional, Dict +from typing import Optional + +import yaml @dataclass @@ -44,8 +45,8 @@ class Platform: soc: str = "" domain: str = "" modeling: str = "" - domain_config: Dict = field(default_factory=dict) - modeling_config: Dict = field(default_factory=dict) + domain_config: dict = field(default_factory=dict) + modeling_config: dict = field(default_factory=dict) runtime: RuntimeConfig = field(default_factory=RuntimeConfig) qemu: QemuConfig = field(default_factory=QemuConfig) boot: BootConfig = field(default_factory=BootConfig) @@ -91,7 +92,7 @@ def from_yaml(cls, path: str) -> "Platform": return cls(**kwargs) -def discover_platforms(root_dir: str) -> Dict[str, "Platform"]: +def discover_platforms(root_dir: str) -> dict[str, "Platform"]: platforms = {} if not os.path.isdir(root_dir): return platforms diff --git a/eosim/core/registry.py b/eosim/core/registry.py index 53e9b1e..2a3ee93 100644 --- a/eosim/core/registry.py +++ b/eosim/core/registry.py @@ -1,18 +1,19 @@ # SPDX-License-Identifier: MIT """Platform registry for querying and filtering platforms.""" -from typing import Dict, List, Optional from collections import defaultdict +from typing import Optional + from eosim.core.platform import Platform, discover_platforms class PlatformRegistry: def __init__(self, root_dir: str = ""): - self._platforms: Dict[str, Platform] = {} + self._platforms: dict[str, Platform] = {} if root_dir: self._platforms = discover_platforms(root_dir) @classmethod - def from_dict(cls, platforms: Dict[str, Platform]) -> "PlatformRegistry": + def from_dict(cls, platforms: dict[str, Platform]) -> "PlatformRegistry": reg = cls.__new__(cls) reg._platforms = dict(platforms) return reg @@ -20,7 +21,7 @@ def from_dict(cls, platforms: Dict[str, Platform]) -> "PlatformRegistry": def count(self) -> int: return len(self._platforms) - def all(self) -> List[Platform]: + def all(self) -> list[Platform]: return list(self._platforms.values()) def get(self, name: str) -> Optional[Platform]: @@ -28,7 +29,7 @@ def get(self, name: str) -> Optional[Platform]: def filter(self, arch: str = None, vendor: str = None, platform_class: str = None, engine: str = None, - domain: str = None) -> List[Platform]: + domain: str = None) -> list[Platform]: results = list(self._platforms.values()) if arch is not None: results = [p for p in results if p.arch.lower() == arch.lower()] @@ -42,14 +43,14 @@ def filter(self, arch: str = None, vendor: str = None, results = [p for p in results if p.domain.lower() == domain.lower()] return results - def group_by(self, field: str) -> Dict[str, List[Platform]]: - groups: Dict[str, List[Platform]] = defaultdict(list) + def group_by(self, field: str) -> dict[str, list[Platform]]: + groups: dict[str, list[Platform]] = defaultdict(list) for p in self._platforms.values(): key = getattr(p, field, "") groups[key].append(p) return dict(groups) - def search(self, query: str) -> List[Platform]: + def search(self, query: str) -> list[Platform]: q = query.lower() results = [] for p in self._platforms.values(): @@ -61,8 +62,8 @@ def search(self, query: str) -> List[Platform]: results.append(p) return results - def stats(self) -> Dict[str, Dict[str, int]]: - st: Dict[str, Dict[str, int]] = { + def stats(self) -> dict[str, dict[str, int]]: + st: dict[str, dict[str, int]] = { "arch": defaultdict(int), "vendor": defaultdict(int), "platform_class": defaultdict(int), @@ -80,11 +81,11 @@ def stats(self) -> Dict[str, Dict[str, int]]: st["domain"][p.domain] += 1 return {k: dict(v) for k, v in st.items()} - def vendors(self) -> List[str]: + def vendors(self) -> list[str]: return sorted({p.vendor for p in self._platforms.values() if p.vendor}) - def arches(self) -> List[str]: + def arches(self) -> list[str]: return sorted({p.arch for p in self._platforms.values() if p.arch}) - def classes(self) -> List[str]: + def classes(self) -> list[str]: return sorted({p.platform_class for p in self._platforms.values() if p.platform_class}) diff --git a/eosim/core/schema.py b/eosim/core/schema.py index c8fb2b4..2f84fa7 100644 --- a/eosim/core/schema.py +++ b/eosim/core/schema.py @@ -1,10 +1,9 @@ # SPDX-License-Identifier: MIT """Platform schema validation constants and helpers.""" -from typing import List, Dict VALID_ARCHES = [ - "arm", "arm64", "aarch64", "riscv64", "x86_64", - "mipsel", "xtensa", "microblaze", "arc", "powerpc", + "arm", "arm64", "aarch64", "riscv64", "riscv32", "x86_64", + "mips", "mipsel", "xtensa", "microblaze", "arc", "powerpc", "tricore", ] VALID_ENGINES = [ @@ -23,10 +22,13 @@ "finite-element", "particle-based", ] -VALID_CLASSES = ["mcu", "sbc", "devboard"] +VALID_CLASSES = [ + "mcu", "sbc", "devboard", "soc", "mpu", "fpga", "virtual", + "automotive", "mobile", "tv", "industrial", "desktop", "safety", +] -def validate_platform(data: Dict) -> List[str]: +def validate_platform(data: dict) -> list[str]: errors = [] if "name" not in data or not data.get("name"): @@ -37,18 +39,18 @@ def validate_platform(data: Dict) -> List[str]: errors.append("missing required field: engine") if data.get("arch") and data["arch"] not in VALID_ARCHES: - errors.append("invalid arch: %s" % data["arch"]) + errors.append("invalid arch: {}".format(data["arch"])) if data.get("engine") and data["engine"] not in VALID_ENGINES: - errors.append("invalid engine: %s" % data["engine"]) + errors.append("invalid engine: {}".format(data["engine"])) if data.get("class") and data["class"] not in VALID_CLASSES: - errors.append("invalid class: %s" % data["class"]) + errors.append("invalid class: {}".format(data["class"])) if data.get("domain") and data["domain"] not in VALID_DOMAINS: - errors.append("invalid domain: %s" % data["domain"]) + errors.append("invalid domain: {}".format(data["domain"])) if data.get("modeling") and data["modeling"] not in VALID_MODELING: - errors.append("invalid modeling: %s" % data["modeling"]) + errors.append("invalid modeling: {}".format(data["modeling"])) return errors diff --git a/eosim/engine/__init__.py b/eosim/engine/__init__.py index 53ec001..d101ddd 100644 --- a/eosim/engine/__init__.py +++ b/eosim/engine/__init__.py @@ -1,3 +1,3 @@ # SPDX-License-Identifier: MIT """Engine package.""" -from eosim.engine.backend import RenodeEngine, QemuEngine, EoSimEngine, SimResult, get_engine \ No newline at end of file +from eosim.engine.backend import EoSimEngine, QemuEngine, RenodeEngine, SimResult, get_engine diff --git a/eosim/engine/backend.py b/eosim/engine/backend.py index e9a0db5..2a630ce 100644 --- a/eosim/engine/backend.py +++ b/eosim/engine/backend.py @@ -1,16 +1,15 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Simulation engine backends — Renode, QEMU, EoSim native, X-Plane, Gazebo, OpenFOAM.""" -import subprocess -import shutil import os +import shutil +import subprocess import time -import threading -from pathlib import Path from dataclasses import dataclass -from typing import Optional, List + from eosim.core.platform import Platform + @dataclass class SimResult: success: bool = False @@ -22,7 +21,7 @@ class SimResult: engine: str = "" platform: str = "" boot_detected: bool = False - artifacts: List[str] = None + artifacts: list[str] = None def __post_init__(self): if self.artifacts is None: @@ -66,7 +65,7 @@ def run(platform: Platform, timeout: int = 60, log_file: str = "") -> SimResult: os.makedirs(os.path.dirname(log_file), exist_ok=True) with open(log_file, "w") as f: f.write("=== EoSim Renode Log ===\n") - f.write("Platform: %s\nArch: %s\n\n" % (platform.name, platform.arch)) + f.write(f"Platform: {platform.name}\nArch: {platform.arch}\n\n") f.write(result.stdout) if result.stderr: f.write("\n=== STDERR ===\n" + result.stderr) @@ -100,14 +99,14 @@ def run(platform: Platform, timeout: int = 60, log_file: str = "") -> SimResult: binary = QemuEngine.ARCH_MAP.get(platform.arch, "qemu-system-" + platform.arch) qemu = shutil.which(binary) if not qemu: - result.stderr = "%s not installed" % binary - result.stdout = "QEMU not available for %s\n" % platform.arch + result.stderr = f"{binary} not installed" + result.stdout = f"QEMU not available for {platform.arch}\n" result.success = True result.boot_detected = False if log_file: os.makedirs(os.path.dirname(log_file), exist_ok=True) with open(log_file, "w") as f: - f.write("QEMU %s not available — dry run\nPASSED (dry run)\n" % binary) + f.write(f"QEMU {binary} not available — dry run\nPASSED (dry run)\n") result.log_file = log_file result.artifacts.append(log_file) return result @@ -149,8 +148,7 @@ def run(platform: Platform, timeout: int = 60, log_file: str = "") -> SimResult: os.makedirs(os.path.dirname(log_file), exist_ok=True) with open(log_file, "w") as f: f.write("=== EoSim QEMU Log ===\n") - f.write("Platform: %s\nArch: %s\nEngine: %s\n\n" % ( - platform.name, platform.arch, binary)) + f.write(f"Platform: {platform.name}\nArch: {platform.arch}\nEngine: {binary}\n\n") f.write(result.stdout) if result.stderr: f.write("\n=== STDERR ===\n" + result.stderr) @@ -195,7 +193,7 @@ def run(platform, timeout=60, log_file=''): os.makedirs(os.path.dirname(log_file) or '.', exist_ok=True) with open(log_file, 'w') as f: f.write('=== EoSim Native Log ===\n') - f.write('Platform: %s\nArch: %s\n\n' % (platform.name, platform.arch)) + f.write(f'Platform: {platform.name}\nArch: {platform.arch}\n\n') f.write(result.stdout) f.write('\n\n' + sim.get('cpu_state', '')) result.log_file = log_file @@ -216,7 +214,7 @@ def available() -> bool: s.connect(('127.0.0.1', 49000)) s.close() return True - except (socket.error, OSError): + except OSError: return False @staticmethod diff --git a/eosim/engine/native/__init__.py b/eosim/engine/native/__init__.py index 36252e1..8c99439 100644 --- a/eosim/engine/native/__init__.py +++ b/eosim/engine/native/__init__.py @@ -1,14 +1,21 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -import time, os +import os +import time from typing import Optional -from eosim.engine.native.memory import MemoryBus, MemoryRegion + from eosim.engine.native.cpu import CPUSimulator, CPUState +from eosim.engine.native.memory import MemoryBus, MemoryRegion from eosim.engine.native.peripherals import ( - UARTDevice, GPIODevice, TimerDevice, SPIDevice, - I2CDevice, InterruptController + GPIODevice, + I2CDevice, + InterruptController, + SPIDevice, + TimerDevice, + UARTDevice, ) + class VirtualMachine: def __init__(self, name: str = 'eosim-vm', arch: str = 'arm64', ram_mb: int = 512, flash_mb: int = 0): @@ -71,7 +78,7 @@ def run(self, max_cycles: int = 100000, timeout_s: float = 30.0) -> dict: self.boot_log.clear() # Boot message - self._uart_print('EoSim Virtual Machine: %s (%s)\\n' % (self.name, self.arch)) + self._uart_print(f'EoSim Virtual Machine: {self.name} ({self.arch})\\n') self._uart_print('RAM: %d MB | Peripherals: %d\\n' % ( sum(r.size for r in self.bus.regions if r.name == 'ram') // (1024*1024), len(self.peripherals))) @@ -81,7 +88,7 @@ def run(self, max_cycles: int = 100000, timeout_s: float = 30.0) -> dict: while self.running and executed < max_cycles: elapsed = time.time() - self.start_time if elapsed > timeout_s: - self._uart_print('\\nTimeout after %.1fs\\n' % elapsed) + self._uart_print(f'\\nTimeout after {elapsed:.1f}s\\n') break if not self.cpu.step(): @@ -126,14 +133,14 @@ def get_status(self) -> dict: 'running': self.running, 'cycles': self.cycles_executed, 'peripherals': list(self.peripherals.keys()), - 'memory_regions': [(r.name, '0x%08X' % r.base, r.size) for r in self.bus.regions], + 'memory_regions': [(r.name, f'0x{r.base:08X}', r.size) for r in self.bus.regions], } def dump_state(self) -> str: - lines = ['=== EoSim VM: %s ===' % self.name] + lines = [f'=== EoSim VM: {self.name} ==='] lines.append(self.cpu.state.dump()) - lines.append('\\nPeripherals: %s' % ', '.join(self.peripherals.keys())) + lines.append('\\nPeripherals: {}'.format(', '.join(self.peripherals.keys()))) lines.append('Memory regions:') for r in self.bus.regions: lines.append(' %-10s 0x%08X %d bytes' % (r.name, r.base, r.size)) - return '\\n'.join(lines) \ No newline at end of file + return '\\n'.join(lines) diff --git a/eosim/engine/native/cpu/__init__.py b/eosim/engine/native/cpu/__init__.py index cc3a25f..2b42c55 100644 --- a/eosim/engine/native/cpu/__init__.py +++ b/eosim/engine/native/cpu/__init__.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project from dataclasses import dataclass, field -from typing import Optional, Callable +from typing import Callable, Optional + @dataclass class CPUState: @@ -27,8 +28,8 @@ def reset(self, entry_point: int = 0, stack_top: int = 0x20000000): self.mode = 'supervisor' def dump(self) -> str: - lines = ['CPU State (%s):' % self.arch] - lines.append(' PC: 0x%08X SP: 0x%08X LR: 0x%08X' % (self.pc, self.sp, self.lr)) + lines = [f'CPU State ({self.arch}):'] + lines.append(f' PC: 0x{self.pc:08X} SP: 0x{self.sp:08X} LR: 0x{self.lr:08X}') lines.append(' CPSR: 0x%08X Mode: %s Cycles: %d' % (self.cpsr, self.mode, self.cycles)) for i in range(0, min(16, len(self.regs)), 4): lines.append(' R%-2d: 0x%08X R%-2d: 0x%08X R%-2d: 0x%08X R%-2d: 0x%08X' % ( @@ -107,4 +108,4 @@ def _execute(self, instr: int): if self.memory: self.memory.write32(addr, self.state.regs[rd]) # More instructions can be added for each architecture - self.trace_log.append((self.state.pc, instr, self.state.cycles)) \ No newline at end of file + self.trace_log.append((self.state.pc, instr, self.state.cycles)) diff --git a/eosim/engine/native/memory/__init__.py b/eosim/engine/native/memory/__init__.py index 284e7da..3f80e43 100644 --- a/eosim/engine/native/memory/__init__.py +++ b/eosim/engine/native/memory/__init__.py @@ -2,7 +2,8 @@ # Copyright (c) 2026 EoS Project import struct from dataclasses import dataclass, field -from typing import Dict, Optional, Callable +from typing import Callable, Dict, Optional + @dataclass class MemoryRegion: @@ -47,7 +48,7 @@ def write32(self, addr: int, val: int): class MemoryBus: def __init__(self): self.regions: list = [] - self.io_handlers: Dict[int, Callable] = {} + self.io_handlers: dict[int, Callable] = {} def add_region(self, region: MemoryRegion): self.regions.append(region) @@ -97,6 +98,6 @@ def load_binary(self, addr: int, data: bytes): def dump(self, addr: int, size: int) -> str: lines = [] for off in range(0, size, 16): - hexs = ' '.join('%02x' % self.read8(addr + off + i) for i in range(min(16, size - off))) - lines.append('%08x: %s' % (addr + off, hexs)) - return '\n'.join(lines) \ No newline at end of file + hexs = ' '.join(f'{self.read8(addr + off + i):02x}' for i in range(min(16, size - off))) + lines.append(f'{addr + off:08x}: {hexs}') + return '\n'.join(lines) diff --git a/eosim/engine/native/peripherals/__init__.py b/eosim/engine/native/peripherals/__init__.py index 5f175e2..df43946 100644 --- a/eosim/engine/native/peripherals/__init__.py +++ b/eosim/engine/native/peripherals/__init__.py @@ -1,7 +1,9 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -import time, threading -from typing import Callable, Optional, List +import threading +import time +from typing import Callable, List, Optional + class UARTDevice: def __init__(self, name: str = 'uart0', base_addr: int = 0x40000000): @@ -221,4 +223,4 @@ def get_highest_pending(self) -> int: if self.pending[i] and self.enabled[i] and self.priority[i] < best_prio: best = i best_prio = self.priority[i] - return best \ No newline at end of file + return best diff --git a/eosim/engine/native/peripherals/actuators_aerodynamics.py b/eosim/engine/native/peripherals/actuators_aerodynamics.py index cc78547..988f282 100644 --- a/eosim/engine/native/peripherals/actuators_aerodynamics.py +++ b/eosim/engine/native/peripherals/actuators_aerodynamics.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -import random from eosim.engine.native.peripherals.actuators import ActuatorBase diff --git a/eosim/engine/native/peripherals/actuators_gaming.py b/eosim/engine/native/peripherals/actuators_gaming.py index d5e8b84..0670cf3 100644 --- a/eosim/engine/native/peripherals/actuators_gaming.py +++ b/eosim/engine/native/peripherals/actuators_gaming.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -import random from eosim.engine.native.peripherals.actuators import ActuatorBase diff --git a/eosim/engine/native/peripherals/actuators_physiology.py b/eosim/engine/native/peripherals/actuators_physiology.py index 3e749a6..a89d548 100644 --- a/eosim/engine/native/peripherals/actuators_physiology.py +++ b/eosim/engine/native/peripherals/actuators_physiology.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -import random from eosim.engine.native.peripherals.actuators import ActuatorBase diff --git a/eosim/engine/native/peripherals/actuators_weather.py b/eosim/engine/native/peripherals/actuators_weather.py index 3ca1bd1..395f0e5 100644 --- a/eosim/engine/native/peripherals/actuators_weather.py +++ b/eosim/engine/native/peripherals/actuators_weather.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Weather domain actuators — weather modification actuator.""" -import random from eosim.engine.native.peripherals.actuators import ActuatorBase diff --git a/eosim/engine/native/peripherals/buses.py b/eosim/engine/native/peripherals/buses.py index 85df0e1..c071bb1 100644 --- a/eosim/engine/native/peripherals/buses.py +++ b/eosim/engine/native/peripherals/buses.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Domain-specific bus protocol peripherals for EoSim simulation.""" -from typing import List, Dict, Optional from collections import deque +from typing import Optional class BusBase: @@ -40,7 +40,7 @@ def __init__(self, name: str = 'can0', base_addr: int = 0x40300000, self.bitrate = bitrate self.tx_queue: deque = deque(maxlen=64) self.rx_queue: deque = deque(maxlen=64) - self.filters: List[int] = [] + self.filters: list[int] = [] self.bus_off = False self.error_count = 0 self.tx_count = 0 @@ -100,8 +100,8 @@ class LINBusController(BusBase): def __init__(self, name: str = 'lin0', base_addr: int = 0x40300100): super().__init__(name, base_addr) - self.schedule_table: List[dict] = [] - self.frame_buffer: Dict[int, bytes] = {} + self.schedule_table: list[dict] = [] + self.frame_buffer: dict[int, bytes] = {} self.current_frame_id = 0 self.master_mode = True @@ -131,16 +131,16 @@ def __init__(self, name: str = 'modbus0', base_addr: int = 0x40300200, super().__init__(name, base_addr) self.mode = mode self.slave_addr = 1 - self.registers: List[int] = [0] * 256 - self.coils: List[bool] = [False] * 256 - self.input_registers: List[int] = [0] * 256 - self.discrete_inputs: List[bool] = [False] * 256 + self.registers: list[int] = [0] * 256 + self.coils: list[bool] = [False] * 256 + self.input_registers: list[int] = [0] * 256 + self.discrete_inputs: list[bool] = [False] * 256 self.transaction_count = 0 - def read_holding(self, addr: int, count: int = 1) -> List[int]: + def read_holding(self, addr: int, count: int = 1) -> list[int]: return self.registers[addr:addr + count] - def write_holding(self, addr: int, values: List[int]): + def write_holding(self, addr: int, values: list[int]): for i, v in enumerate(values): if addr + i < len(self.registers): self.registers[addr + i] = v & 0xFFFF diff --git a/eosim/engine/native/peripherals/sensors_finance.py b/eosim/engine/native/peripherals/sensors_finance.py index f90277a..9b25e54 100644 --- a/eosim/engine/native/peripherals/sensors_finance.py +++ b/eosim/engine/native/peripherals/sensors_finance.py @@ -2,7 +2,6 @@ # Copyright (c) 2026 EoS Project """Finance domain sensors — market feed, order book.""" import random -import math from eosim.engine.native.peripherals.sensors import SensorBase diff --git a/eosim/engine/native/peripherals/sensors_gaming.py b/eosim/engine/native/peripherals/sensors_gaming.py index f054661..926a918 100644 --- a/eosim/engine/native/peripherals/sensors_gaming.py +++ b/eosim/engine/native/peripherals/sensors_gaming.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Gaming domain sensors — physics engine, terrain sensor, entity manager.""" -import math -import random from eosim.engine.native.peripherals.sensors import SensorBase diff --git a/eosim/engine/native/peripherals/sensors_physiology.py b/eosim/engine/native/peripherals/sensors_physiology.py index 2004a27..d8265bf 100644 --- a/eosim/engine/native/peripherals/sensors_physiology.py +++ b/eosim/engine/native/peripherals/sensors_physiology.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Physiology domain sensors — heart model, lung model, blood pressure.""" -import math import random from eosim.engine.native.peripherals.sensors import SensorBase diff --git a/eosim/engine/native/peripherals/sensors_weather.py b/eosim/engine/native/peripherals/sensors_weather.py index 513d4e5..47d5393 100644 --- a/eosim/engine/native/peripherals/sensors_weather.py +++ b/eosim/engine/native/peripherals/sensors_weather.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Weather domain sensors — weather station, anemometer, radar.""" -import math import random from eosim.engine.native.peripherals.sensors import SensorBase diff --git a/eosim/engine/native/peripherals/wireless.py b/eosim/engine/native/peripherals/wireless.py index 960382e..c646b95 100644 --- a/eosim/engine/native/peripherals/wireless.py +++ b/eosim/engine/native/peripherals/wireless.py @@ -2,7 +2,6 @@ # Copyright (c) 2026 EoS Project """Wireless communication peripherals for EoSim simulation.""" import random -from collections import deque class WirelessBase: diff --git a/eosim/engine/native/simulators/__init__.py b/eosim/engine/native/simulators/__init__.py index 6a707af..2f12eb3 100644 --- a/eosim/engine/native/simulators/__init__.py +++ b/eosim/engine/native/simulators/__init__.py @@ -5,24 +5,24 @@ All simulators are pure Python, cross-platform (Linux/Windows/macOS). No OS-specific dependencies. No tkinter, no C extensions. """ -from eosim.engine.native.simulators.vehicle import VehicleSimulator -from eosim.engine.native.simulators.drone import DroneSimulator -from eosim.engine.native.simulators.robot import RobotSimulator +from eosim.engine.native.simulators.aerodynamics import AerodynamicsSimulator from eosim.engine.native.simulators.aircraft import AircraftSimulator -from eosim.engine.native.simulators.medical import MedicalSimulator +from eosim.engine.native.simulators.camera import HomeCameraSimulator +from eosim.engine.native.simulators.drone import DroneSimulator +from eosim.engine.native.simulators.energy import EnergySimulator +from eosim.engine.native.simulators.finance import FinanceSimulator +from eosim.engine.native.simulators.gaming import GamingSimulator from eosim.engine.native.simulators.industrial import IndustrialSimulator from eosim.engine.native.simulators.iot import IoTSimulator -from eosim.engine.native.simulators.satellite import SatelliteSimulator -from eosim.engine.native.simulators.energy import EnergySimulator -from eosim.engine.native.simulators.wearable import WearableSimulator from eosim.engine.native.simulators.media import MediaDeviceSimulator -from eosim.engine.native.simulators.speaker import SmartSpeakerSimulator -from eosim.engine.native.simulators.camera import HomeCameraSimulator -from eosim.engine.native.simulators.aerodynamics import AerodynamicsSimulator +from eosim.engine.native.simulators.medical import MedicalSimulator from eosim.engine.native.simulators.physiology import PhysiologySimulator -from eosim.engine.native.simulators.finance import FinanceSimulator +from eosim.engine.native.simulators.robot import RobotSimulator +from eosim.engine.native.simulators.satellite import SatelliteSimulator +from eosim.engine.native.simulators.speaker import SmartSpeakerSimulator +from eosim.engine.native.simulators.vehicle import VehicleSimulator +from eosim.engine.native.simulators.wearable import WearableSimulator from eosim.engine.native.simulators.weather import WeatherSimulator -from eosim.engine.native.simulators.gaming import GamingSimulator class BaseSimulator: diff --git a/eosim/engine/native/simulators/aerodynamics.py b/eosim/engine/native/simulators/aerodynamics.py index e317370..b18edfc 100644 --- a/eosim/engine/native/simulators/aerodynamics.py +++ b/eosim/engine/native/simulators/aerodynamics.py @@ -50,10 +50,16 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors_aerodynamics import ( - WindTunnelSensor, AirflowSensor, PitotTube, ForceBalance) from eosim.engine.native.peripherals.actuators_aerodynamics import ( - AeroActuator, TunnelFanController) + AeroActuator, + TunnelFanController, + ) + from eosim.engine.native.peripherals.sensors_aerodynamics import ( + AirflowSensor, + ForceBalance, + PitotTube, + WindTunnelSensor, + ) self.vm.add_peripheral('wind_tunnel', WindTunnelSensor('wind_tunnel', 0x40110000)) self.vm.add_peripheral('airflow', AirflowSensor('airflow', 0x40110100)) @@ -92,7 +98,7 @@ def tick(self): tunnel = self.vm.peripherals.get('wind_tunnel') airflow = self.vm.peripherals.get('airflow') balance = self.vm.peripherals.get('balance') - fan = self.vm.peripherals.get('fan') + self.vm.peripherals.get('fan') airspeed = tunnel.airspeed_mps if tunnel else 0 self.state['airspeed_mps'] = round(airspeed, 2) @@ -170,7 +176,7 @@ def get_peripherals(self): return dict(self.vm.peripherals) def get_status_text(self): - scn = " [%s]" % self.scenario if self.scenario else "" + scn = f" [{self.scenario}]" if self.scenario else "" return "%s | Tick %d%s" % (self.DISPLAY_NAME, self.tick_count, scn) def reset(self): diff --git a/eosim/engine/native/simulators/aircraft.py b/eosim/engine/native/simulators/aircraft.py index 8179683..8e12962 100644 --- a/eosim/engine/native/simulators/aircraft.py +++ b/eosim/engine/native/simulators/aircraft.py @@ -1,115 +1,10 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -"""Aircraft simulator. Pure Python, cross-platform.""" -import math, random - - -class AircraftSimulator: - PRODUCT_TYPE = 'aircraft' - DISPLAY_NAME = 'Aircraft' - SCENARIOS = { - 'takeoff': {'description': 'Takeoff roll and climb'}, - 'cruise': {'alt_ft': 35000, 'spd_kts': 250, 'description': 'Level cruise'}, - 'approach': {'alt_ft': 3000, 'spd_kts': 140, 'description': 'Final approach'}, - 'landing': {'description': 'Flare and touchdown'}, - 'engine_failure': {'description': 'Engine out'}, - 'stall_recovery': {'description': 'Stall recovery'}, - 'autopilot_engage': {'description': 'Engage autopilot'}, - } - - def __init__(self, vm): - self.vm = vm; self.tick_count = 0; self.state = {} - self.scenario = ''; self._scenario_step = 0; self._ap = False; self._tgt_alt = 0; self._tgt_spd = 0 - - def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, PressureSensor - from eosim.engine.native.peripherals.actuators import ServoController, ThrottleActuator - from eosim.engine.native.peripherals.buses import ARINC429 - self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200, 9)) - self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) - self.vm.add_peripheral('baro0', PressureSensor('baro0', 0x40100100)) - self.vm.add_peripheral('servo0', ServoController('servo0', 0x40200100, 4)) - self.vm.add_peripheral('throttle', ThrottleActuator('throttle', 0x40200900)) - self.vm.add_peripheral('arinc0', ARINC429('arinc0', 0x40300400)) - self.state = {'altitude_ft': 0, 'airspeed_kts': 0, 'heading_deg': 0, 'vs_fpm': 0, - 'roll_deg': 0, 'pitch_deg': 0, 'aoa_deg': 2.0, 'stall_warning': False, - 'autopilot': False, 'gear_down': True, 'scenario': '', 'lift_n': 0, 'drag_n': 0} - - def load_scenario(self, name): - if name not in self.SCENARIOS: return - self.scenario = name; self._scenario_step = 0; self.state['scenario'] = name - if name == 'cruise': - self._tgt_alt = self.SCENARIOS[name]['alt_ft']; self._tgt_spd = self.SCENARIOS[name]['spd_kts']; self._ap = True; self.state['autopilot'] = True - elif name == 'approach': - self._tgt_alt = self.SCENARIOS[name]['alt_ft']; self._tgt_spd = self.SCENARIOS[name]['spd_kts'] - elif name == 'stall_recovery': self.state['aoa_deg'] = 14.0 - elif name == 'autopilot_engage': - self._ap = True; self.state['autopilot'] = True; self._tgt_alt = self.state.get('altitude_ft', 10000); self._tgt_spd = self.state.get('airspeed_kts', 200) - - def tick(self): - self.tick_count += 1 - for n, d in self.vm.peripherals.items(): - if hasattr(d, 'simulate_tick'): d.simulate_tick() - throttle = self.vm.peripherals.get('throttle'); baro = self.vm.peripherals.get('baro0') - servo = self.vm.peripherals.get('servo0'); gps = self.vm.peripherals.get('gps0') - self._apply_scenario(throttle, servo) - thr = throttle.position_pct if throttle else 0; speed = self.state.get('airspeed_kts', 0) - aoa = self.state.get('aoa_deg', 2.0) - cl = 0.1 + 0.1 * aoa; cd = 0.02 + 0.005 * aoa * aoa - q = 0.5 * 1.225 * (speed * 0.5144) ** 2; ws = 30.0 - lift = cl * q * ws; drag = cd * q * ws - self.state['lift_n'] = round(lift); self.state['drag_n'] = round(drag) - speed = max(0, speed + (thr * 50 - drag) * 0.0001); self.state['airspeed_kts'] = round(speed, 1) - self.state['stall_warning'] = 0 < speed < 66 - pitch = self.state.get('pitch_deg', 0); vs = pitch * speed * 0.3; self.state['vs_fpm'] = round(vs) - alt = max(0, self.state.get('altitude_ft', 0) + vs / 60 * 0.1); self.state['altitude_ft'] = round(alt) - if servo: - self.state['roll_deg'] = round((servo.positions[0] - 90) * 2, 1) - self.state['pitch_deg'] = round((servo.positions[1] - 90) * 1.5, 1) - self.state['heading_deg'] = round((self.state.get('heading_deg', 0) + self.state['roll_deg'] * 0.05) % 360, 1) - if baro: baro.set_altitude(alt * 0.3048) - if gps: gps.speed_mps = speed * 0.5144; gps.heading_deg = self.state['heading_deg'] - self._scenario_step += 1 - - def _apply_scenario(self, throttle, servo): - if not self.scenario: - if self._ap: self._ap_ctrl(throttle, servo) - return - if self.scenario == 'takeoff': - if throttle: throttle.target_pct = 100 - if self.state['airspeed_kts'] > 70 and servo: servo.set_target(1, 100); self.state['gear_down'] = False - elif self.scenario in ('cruise', 'autopilot_engage'): self._ap_ctrl(throttle, servo) - elif self.scenario == 'approach': - if throttle: throttle.target_pct = max(0, min(100, 30 + (self._tgt_spd - self.state.get('airspeed_kts', 0)) * 2)) - if servo: servo.set_target(1, 90 + max(-10, min(10, (self._tgt_alt - self.state.get('altitude_ft', 0)) * 0.01))) - elif self.scenario == 'engine_failure': - if throttle: throttle.target_pct = 0 - if servo: servo.set_target(1, 85) - elif self.scenario == 'stall_recovery': - if self.state.get('stall_warning'): - if servo: servo.set_target(1, 80) - if throttle: throttle.target_pct = 100 - self.state['aoa_deg'] = max(2, self.state.get('aoa_deg', 14) - 0.5) - elif self.scenario == 'landing': - if throttle: throttle.target_pct = max(0, throttle.position_pct - 0.5) - self.state['gear_down'] = True - - def _ap_ctrl(self, throttle, servo): - if throttle: throttle.target_pct = max(0, min(100, 50 + (self._tgt_spd - self.state.get('airspeed_kts', 0)) * 1.5)) - if servo: servo.set_target(1, 90 + max(-10, min(10, (self._tgt_alt - self.state.get('altitude_ft', 0)) * 0.005))); servo.set_target(0, 90) - - def get_state(self): return dict(self.state) - def get_peripherals(self): return dict(self.vm.peripherals) - def get_status_text(self): return f"{self.DISPLAY_NAME} | ALT {self.state.get('altitude_ft',0)}ft | Tick {self.tick_count}" - def reset(self): self.tick_count = 0; self.state = {}; self.scenario = ''; self._ap = False -# SPDX-License-Identifier: MIT -# Copyright (c) 2026 EoS Project """Aircraft simulator — fixed-wing / helicopter with flight dynamics. Pure Python, cross-platform. No OS-specific dependencies. """ import math -import random class AircraftSimulator: @@ -137,9 +32,9 @@ def __init__(self, vm): self._target_speed = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, PressureSensor from eosim.engine.native.peripherals.actuators import ServoController, ThrottleActuator from eosim.engine.native.peripherals.buses import ARINC429 + from eosim.engine.native.peripherals.sensors import GPSModule, IMUSensor, PressureSensor self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200, 9)) self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) @@ -192,7 +87,7 @@ def load_scenario(self, name: str): def tick(self): self.tick_count += 1 - for name, dev in self.vm.peripherals.items(): + for _, dev in self.vm.peripherals.items(): if hasattr(dev, 'simulate_tick'): dev.simulate_tick() @@ -216,7 +111,6 @@ def tick(self): wing_area = 30.0 lift = cl * q * wing_area drag = cd * q * wing_area - weight = 15000 * 9.81 self.state['lift_n'] = round(lift, 0) self.state['drag_n'] = round(drag, 0) diff --git a/eosim/engine/native/simulators/camera.py b/eosim/engine/native/simulators/camera.py index 29ef85d..0cf832d 100644 --- a/eosim/engine/native/simulators/camera.py +++ b/eosim/engine/native/simulators/camera.py @@ -26,9 +26,9 @@ def __init__(self, vm): self._motion_cooldown = 0 def setup(self): + from eosim.engine.native.peripherals.composites import BatteryManagement, RTCModule + from eosim.engine.native.peripherals.sensors import LightSensor, TemperatureSensor from eosim.engine.native.peripherals.wireless import WiFiModule - from eosim.engine.native.peripherals.sensors import TemperatureSensor, LightSensor - from eosim.engine.native.peripherals.composites import RTCModule, BatteryManagement self.vm.add_peripheral('wifi0', WiFiModule('wifi0', 0x40400000)) self.vm.add_peripheral('temp0', TemperatureSensor('temp0', 0x40100000)) diff --git a/eosim/engine/native/simulators/drone.py b/eosim/engine/native/simulators/drone.py index bd8f749..ab46e2b 100644 --- a/eosim/engine/native/simulators/drone.py +++ b/eosim/engine/native/simulators/drone.py @@ -50,9 +50,9 @@ def __init__(self, vm): self._wind = [0, 0] def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, PressureSensor from eosim.engine.native.peripherals.actuators import ESCController from eosim.engine.native.peripherals.composites import BatteryManagement + from eosim.engine.native.peripherals.sensors import GPSModule, IMUSensor, PressureSensor self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200, 9)) self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) @@ -175,7 +175,7 @@ def tick(self): if gps: dlat = self._home_lat - gps.latitude dlon = self._home_lon - gps.longitude - dist = math.sqrt(dlat ** 2 + dlon ** 2) * 111320 + dist = math.hypot(dlat, dlon) * 111320 if dist > 2: pitch_cmd = min(15, dist * 0.5) gps.heading_deg = math.degrees(math.atan2(dlon, dlat)) % 360 @@ -191,7 +191,7 @@ def tick(self): dlat = wp[0] - gps.latitude dlon = wp[1] - gps.longitude self._target_alt = wp[2] - dist = math.sqrt(dlat ** 2 + dlon ** 2) * 111320 + dist = math.hypot(dlat, dlon) * 111320 if dist < 5: self._waypoint_idx += 1 else: diff --git a/eosim/engine/native/simulators/energy.py b/eosim/engine/native/simulators/energy.py index 168d3d4..9019b41 100644 --- a/eosim/engine/native/simulators/energy.py +++ b/eosim/engine/native/simulators/energy.py @@ -1,81 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -"""Energy / Power system simulator. Pure Python, cross-platform.""" -import math, random - - -class EnergySimulator: - PRODUCT_TYPE = 'energy' - DISPLAY_NAME = 'Energy System' - SCENARIOS = { - 'mppt_tracking': {'description': 'Solar MPPT peak power tracking'}, - 'grid_sync': {'target_freq': 60.0, 'description': 'Sync inverter to grid'}, - 'charge_discharge': {'description': 'Battery charge/discharge cycle'}, - 'islanding': {'description': 'Grid disconnect, island mode'}, - 'load_shedding': {'max_load_w': 2000, 'description': 'Shed non-critical loads'}, - } - - def __init__(self, vm): - self.vm = vm; self.tick_count = 0; self.state = {} - self.scenario = ''; self._scenario_step = 0 - - def setup(self): - from eosim.engine.native.peripherals.sensors import ADCChannel, TemperatureSensor, CurrentSensor - from eosim.engine.native.peripherals.actuators import RelayBank - from eosim.engine.native.peripherals.buses import ModbusController - from eosim.engine.native.peripherals.composites import BatteryManagement, RTCModule - self.vm.add_peripheral('solar_adc', ADCChannel('solar_adc', 0x40100600, 4)) - self.vm.add_peripheral('current0', CurrentSensor('current0', 0x40100700)) - self.vm.add_peripheral('temp0', TemperatureSensor('temp0', 0x40100000, -20, 80)) - self.vm.add_peripheral('relay0', RelayBank('relay0', 0x40200500)) - self.vm.add_peripheral('modbus0', ModbusController('modbus0', 0x40300200)) - self.vm.add_peripheral('bms0', BatteryManagement('bms0', 0x40500000, 16, 100000)) - self.vm.add_peripheral('rtc0', RTCModule('rtc0', 0x40500400)) - self.state = {'solar_power_w': 0, 'grid_power_w': 0, 'load_power_w': 500, - 'battery_soc': 60, 'grid_frequency_hz': 60.0, 'mode': 'GRID_TIE', - 'mppt_voltage': 0, 'island_mode': False, 'scenario': ''} - - def load_scenario(self, name): - if name not in self.SCENARIOS: return - self.scenario = name; self._scenario_step = 0; self.state['scenario'] = name - if name == 'islanding': self.state['island_mode'] = True; self.state['mode'] = 'ISLAND' - elif name == 'load_shedding': self.state['load_power_w'] = 3000 - - def tick(self): - self.tick_count += 1 - for n, d in self.vm.peripherals.items(): - if hasattr(d, 'simulate_tick'): d.simulate_tick() - hour = (self.tick_count % 2400) / 100 - solar = max(0, 3000 * math.sin(math.pi * (hour - 6) / 12)) if 6 <= hour <= 18 else 0 - solar += random.gauss(0, 50) - self.state['solar_power_w'] = round(max(0, solar)) - self.state['grid_frequency_hz'] = 60.0 + random.gauss(0, 0.02) - if self.scenario == 'mppt_tracking': self.state['mppt_voltage'] = round(solar * 0.12 + random.gauss(0, 0.5), 1) - elif self.scenario == 'islanding': self.state['grid_power_w'] = 0; self.state['mode'] = 'ISLAND' - elif self.scenario == 'load_shedding': - relay = self.vm.peripherals.get('relay0') - if self.state['load_power_w'] > 2000 and relay: relay.states[7] = False; self.state['load_power_w'] = 2000 - elif self.scenario == 'charge_discharge': - bms = self.vm.peripherals.get('bms0') - if bms: bms.charging = (self._scenario_step % 400) < 200 - bms = self.vm.peripherals.get('bms0') - modbus = self.vm.peripherals.get('modbus0') - if bms: - surplus = solar - self.state['load_power_w'] - bms.charging = surplus > 0; bms.current_ma = abs(surplus) * 0.2 - self.state['battery_soc'] = round(bms.soc_percent, 1) - if modbus: - modbus.registers[0] = int(self.state['solar_power_w']) - modbus.registers[1] = int(self.state['battery_soc'] * 10) - self._scenario_step += 1 - - def get_state(self): return dict(self.state) - def get_peripherals(self): return dict(self.vm.peripherals) - def get_status_text(self): return f"{self.DISPLAY_NAME} | {self.state.get('mode','GRID_TIE')} | SOC {self.state.get('battery_soc',0)}% | Tick {self.tick_count}" - def reset(self): self.tick_count = 0; self.state = {}; self.scenario = '' -# SPDX-License-Identifier: MIT -# Copyright (c) 2026 EoS Project -"""Energy / Power system simulator. Pure Python, cross-platform.""" +"""Energy / Power system simulator. Pure Python, cross-platform. No OS-specific dependencies.""" import math import random @@ -100,10 +25,14 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import ADCChannel, TemperatureSensor, CurrentSensor from eosim.engine.native.peripherals.actuators import RelayBank from eosim.engine.native.peripherals.buses import ModbusController from eosim.engine.native.peripherals.composites import BatteryManagement, RTCModule + from eosim.engine.native.peripherals.sensors import ( + ADCChannel, + CurrentSensor, + TemperatureSensor, + ) self.vm.add_peripheral('solar_adc', ADCChannel('solar_adc', 0x40100600, 4)) self.vm.add_peripheral('current0', CurrentSensor('current0', 0x40100700)) @@ -134,7 +63,7 @@ def load_scenario(self, name: str): def tick(self): self.tick_count += 1 - for name, dev in self.vm.peripherals.items(): + for _, dev in self.vm.peripherals.items(): if hasattr(dev, 'simulate_tick'): dev.simulate_tick() diff --git a/eosim/engine/native/simulators/finance.py b/eosim/engine/native/simulators/finance.py index b0ec6d0..b96d67a 100644 --- a/eosim/engine/native/simulators/finance.py +++ b/eosim/engine/native/simulators/finance.py @@ -5,7 +5,6 @@ Pure Python, cross-platform. No OS-specific dependencies. """ import math -import random class FinanceSimulator: @@ -50,10 +49,8 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors_finance import ( - MarketFeed, OrderBook) - from eosim.engine.native.peripherals.actuators_finance import ( - TradeExecutor, RiskEngine) + from eosim.engine.native.peripherals.actuators_finance import RiskEngine, TradeExecutor + from eosim.engine.native.peripherals.sensors_finance import MarketFeed, OrderBook self.vm.add_peripheral('market', MarketFeed('market', 0x40130000)) self.vm.add_peripheral('orderbook', OrderBook('orderbook', 0x40130100)) @@ -113,7 +110,6 @@ def tick(self): if risk: self.state['var_95'] = round(risk.current_var, 2) - returns = [] if market and market.price > 0 and self.state['open'] > 0: ret = (market.price - self.state['open']) / self.state['open'] self.state['volatility'] = round(abs(ret) * math.sqrt(252), 4) diff --git a/eosim/engine/native/simulators/gaming.py b/eosim/engine/native/simulators/gaming.py index d30a7d6..5263111 100644 --- a/eosim/engine/native/simulators/gaming.py +++ b/eosim/engine/native/simulators/gaming.py @@ -50,9 +50,12 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors_gaming import ( - PhysicsEngine, TerrainSensor, EntityManager) from eosim.engine.native.peripherals.actuators_gaming import GameController + from eosim.engine.native.peripherals.sensors_gaming import ( + EntityManager, + PhysicsEngine, + TerrainSensor, + ) self.vm.add_peripheral('physics', PhysicsEngine('physics', 0x40150000)) self.vm.add_peripheral('terrain', TerrainSensor('terrain', 0x40150100)) diff --git a/eosim/engine/native/simulators/industrial.py b/eosim/engine/native/simulators/industrial.py index 2f8a0cc..a403ed2 100644 --- a/eosim/engine/native/simulators/industrial.py +++ b/eosim/engine/native/simulators/industrial.py @@ -1,101 +1,9 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -"""Industrial PLC simulator. Pure Python, cross-platform.""" -import random - - -class IndustrialSimulator: - PRODUCT_TYPE = 'industrial' - DISPLAY_NAME = 'Industrial PLC' - SCENARIOS = { - 'conveyor_sorting': {'speed_rpm': 500, 'description': 'Conveyor sorting'}, - 'batch_mixing': {'temp_target': 65, 'description': 'Temp-controlled mixing'}, - 'cnc_positioning': {'description': 'CNC positioning'}, - 'emergency_stop': {'description': 'E-stop shutdown'}, - 'redundancy_failover': {'description': 'Backup takeover'}, - } - - def __init__(self, vm): - self.vm = vm; self.tick_count = 0; self.state = {} - self.scenario = ''; self._scenario_step = 0 - - def setup(self): - from eosim.engine.native.peripherals.sensors import TemperatureSensor, ProximitySensor, ADCChannel - from eosim.engine.native.peripherals.actuators import MotorController, ValveController, RelayBank - from eosim.engine.native.peripherals.buses import ModbusController - from eosim.engine.native.peripherals.composites import WatchdogTimer - self.vm.add_peripheral('modbus0', ModbusController('modbus0', 0x40300200)) - self.vm.add_peripheral('motor0', MotorController('motor0', 0x40200000)) - self.vm.add_peripheral('valve0', ValveController('valve0', 0x40200300)) - self.vm.add_peripheral('relay0', RelayBank('relay0', 0x40200500)) - self.vm.add_peripheral('temp0', TemperatureSensor('temp0', 0x40100000, 0, 200)) - self.vm.add_peripheral('prox0', ProximitySensor('prox0', 0x40100400)) - self.vm.add_peripheral('adc0', ADCChannel('adc0', 0x40100600)) - self.vm.add_peripheral('wdt0', WatchdogTimer('wdt0', 0x40500200)) - self.state = {'conveyor_speed': 0, 'process_temp': 22, 'product_count': 0, - 'e_stop': False, 'mode': 'IDLE', 'scenario': '', 'batch_complete': False} - - def load_scenario(self, name): - if name not in self.SCENARIOS: return - self.scenario = name; self._scenario_step = 0; self.state['scenario'] = name - motor = self.vm.peripherals.get('motor0') - if name == 'conveyor_sorting': - if motor: motor.enabled = True; motor.target_speed = 500 - self.state['mode'] = 'RUNNING' - elif name == 'batch_mixing': self.state['mode'] = 'RUNNING' - elif name == 'cnc_positioning': - if motor: motor.enabled = True - self.state['mode'] = 'RUNNING' - elif name == 'emergency_stop': - self.state['e_stop'] = True; self.state['mode'] = 'E_STOP' - if motor: motor.enabled = False; motor.target_speed = 0 - elif name == 'redundancy_failover': self.state['mode'] = 'FAILOVER' - - def tick(self): - self.tick_count += 1 - for n, d in self.vm.peripherals.items(): - if hasattr(d, 'simulate_tick'): d.simulate_tick() - motor = self.vm.peripherals.get('motor0') - temp = self.vm.peripherals.get('temp0') - prox = self.vm.peripherals.get('prox0') - modbus = self.vm.peripherals.get('modbus0') - valve = self.vm.peripherals.get('valve0') - if self.scenario == 'batch_mixing' and temp: - target = self.SCENARIOS['batch_mixing']['temp_target'] - err = target - temp.temperature - if valve: valve.positions[0] = max(0, min(100, err * 5)) - temp.temperature += err * 0.02 - if abs(err) < 1 and self._scenario_step > 100: self.state['batch_complete'] = True - elif self.scenario == 'cnc_positioning' and motor: - step = self._scenario_step % 100 - motor.target_speed = 200 if step < 25 else (-200 if 50 <= step < 75 else 0) - elif self.scenario == 'emergency_stop' and motor: - motor.enabled = False; motor.target_speed = 0 - elif self.scenario == 'redundancy_failover' and self._scenario_step > 20: - self.state['mode'] = 'RUNNING' - if motor: motor.enabled = True - if motor: self.state['conveyor_speed'] = motor.speed_rpm - if temp: self.state['process_temp'] = round(temp.temperature, 1) - if prox and prox.detected: self.state['product_count'] += 1 - if modbus: - modbus.registers[0] = int(self.state['conveyor_speed']) - modbus.registers[1] = int(self.state['process_temp'] * 10) - if self.state['e_stop']: self.state['mode'] = 'E_STOP' - elif motor and motor.enabled: self.state['mode'] = 'RUNNING' - elif self.state['mode'] != 'FAILOVER': self.state['mode'] = 'IDLE' - self._scenario_step += 1 - - def get_state(self): return dict(self.state) - def get_peripherals(self): return dict(self.vm.peripherals) - def get_status_text(self): return f"{self.DISPLAY_NAME} | {self.state.get('mode','IDLE')} | Tick {self.tick_count}" - def reset(self): self.tick_count = 0; self.state = {}; self.scenario = '' -# SPDX-License-Identifier: MIT -# Copyright (c) 2026 EoS Project """Industrial PLC simulator — conveyor, process control, e-stop. Pure Python, cross-platform. No OS-specific dependencies. """ -import random class IndustrialSimulator: @@ -118,10 +26,18 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import TemperatureSensor, ProximitySensor, ADCChannel - from eosim.engine.native.peripherals.actuators import MotorController, ValveController, RelayBank + from eosim.engine.native.peripherals.actuators import ( + MotorController, + RelayBank, + ValveController, + ) from eosim.engine.native.peripherals.buses import ModbusController from eosim.engine.native.peripherals.composites import WatchdogTimer + from eosim.engine.native.peripherals.sensors import ( + ADCChannel, + ProximitySensor, + TemperatureSensor, + ) self.vm.add_peripheral('modbus0', ModbusController('modbus0', 0x40300200)) self.vm.add_peripheral('motor0', MotorController('motor0', 0x40200000)) @@ -168,7 +84,7 @@ def load_scenario(self, name: str): def tick(self): self.tick_count += 1 - for name, dev in self.vm.peripherals.items(): + for _, dev in self.vm.peripherals.items(): if hasattr(dev, 'simulate_tick'): dev.simulate_tick() diff --git a/eosim/engine/native/simulators/iot.py b/eosim/engine/native/simulators/iot.py index 7a0eb96..3807228 100644 --- a/eosim/engine/native/simulators/iot.py +++ b/eosim/engine/native/simulators/iot.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """IoT / Smart Home simulator. Pure Python, cross-platform.""" -import random class IoTSimulator: @@ -25,10 +24,14 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import TemperatureSensor, LightSensor, ADCChannel from eosim.engine.native.peripherals.actuators import RelayBank - from eosim.engine.native.peripherals.wireless import WiFiModule, BLEModule from eosim.engine.native.peripherals.composites import BatteryManagement, RTCModule + from eosim.engine.native.peripherals.sensors import ( + ADCChannel, + LightSensor, + TemperatureSensor, + ) + from eosim.engine.native.peripherals.wireless import BLEModule, WiFiModule self.vm.add_peripheral('temp0', TemperatureSensor('temp0', 0x40100000)) self.vm.add_peripheral('light0', LightSensor('light0', 0x40100500)) diff --git a/eosim/engine/native/simulators/media.py b/eosim/engine/native/simulators/media.py index 5637cdf..c63114c 100644 --- a/eosim/engine/native/simulators/media.py +++ b/eosim/engine/native/simulators/media.py @@ -25,9 +25,9 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.wireless import WiFiModule, BLEModule from eosim.engine.native.peripherals.actuators import DisplayDriver from eosim.engine.native.peripherals.composites import RTCModule + from eosim.engine.native.peripherals.wireless import BLEModule, WiFiModule self.vm.add_peripheral('wifi0', WiFiModule('wifi0', 0x40400000)) self.vm.add_peripheral('ble0', BLEModule('ble0', 0x40400100)) diff --git a/eosim/engine/native/simulators/medical.py b/eosim/engine/native/simulators/medical.py index c91bec7..0681ea3 100644 --- a/eosim/engine/native/simulators/medical.py +++ b/eosim/engine/native/simulators/medical.py @@ -4,7 +4,6 @@ Pure Python, cross-platform. No OS-specific dependencies. """ -import math import random @@ -37,9 +36,13 @@ def __init__(self, vm): self._profile = 'normal_adult' def setup(self): - from eosim.engine.native.peripherals.sensors import ECGSensor, PulseOximeter, TemperatureSensor - from eosim.engine.native.peripherals.actuators import PumpController, DisplayDriver - from eosim.engine.native.peripherals.composites import WatchdogTimer, BatteryManagement + from eosim.engine.native.peripherals.actuators import DisplayDriver, PumpController + from eosim.engine.native.peripherals.composites import BatteryManagement, WatchdogTimer + from eosim.engine.native.peripherals.sensors import ( + ECGSensor, + PulseOximeter, + TemperatureSensor, + ) self.vm.add_peripheral('ecg0', ECGSensor('ecg0', 0x40100800)) self.vm.add_peripheral('spo2_0', PulseOximeter('spo2_0', 0x40100900)) diff --git a/eosim/engine/native/simulators/physiology.py b/eosim/engine/native/simulators/physiology.py index 44d87a3..b929e91 100644 --- a/eosim/engine/native/simulators/physiology.py +++ b/eosim/engine/native/simulators/physiology.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Physiology simulator — patient cardiovascular, respiratory, pharmacokinetics.""" -import math import random @@ -40,10 +39,16 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors_physiology import ( - HeartModel, LungModel, BloodPressureSensor) from eosim.engine.native.peripherals.actuators_physiology import ( - VentilatorActuator, InfusionPump, SurgicalTool) + InfusionPump, + SurgicalTool, + VentilatorActuator, + ) + from eosim.engine.native.peripherals.sensors_physiology import ( + BloodPressureSensor, + HeartModel, + LungModel, + ) self.vm.add_peripheral('heart', HeartModel('heart', 0x40120000)) self.vm.add_peripheral('lung', LungModel('lung', 0x40120100)) self.vm.add_peripheral('bp', BloodPressureSensor('bp', 0x40120200)) @@ -93,7 +98,7 @@ def get_peripherals(self): return dict(self.vm.peripherals) def get_status_text(self): - scn = " [%s]" % self.scenario if self.scenario else "" + scn = f" [{self.scenario}]" if self.scenario else "" return "%s | Tick %d%s" % (self.DISPLAY_NAME, self.tick_count, scn) def reset(self): diff --git a/eosim/engine/native/simulators/robot.py b/eosim/engine/native/simulators/robot.py index 6b8fa28..7b72c57 100644 --- a/eosim/engine/native/simulators/robot.py +++ b/eosim/engine/native/simulators/robot.py @@ -1,91 +1,10 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project -"""Robot simulator. Pure Python, cross-platform.""" -import math - - -class RobotSimulator: - PRODUCT_TYPE = 'robot' - DISPLAY_NAME = 'Robot Controller' - SCENARIOS = { - 'home_calibration': {'description': 'Move all joints to home position'}, - 'pick_and_place': {'pick': [45, 60, 120, 90, 45, 90], 'place': [135, 60, 120, 90, 135, 90], 'description': 'Pick and place'}, - 'path_planning': {'waypoints': [[90]*6, [60,70,110,80,100,90], [120,50,130,100,80,90], [90]*6], 'description': 'Joint-space path'}, - 'obstacle_avoidance': {'safe_cm': 30, 'description': 'Stop on obstacle'}, - 'force_assembly': {'target_n': 5.0, 'description': 'Force-controlled insertion'}, - } - LINK_LENGTHS = [0.2, 0.3, 0.25, 0.1, 0.1, 0.05] - - def __init__(self, vm): - self.vm = vm; self.tick_count = 0; self.state = {} - self.scenario = ''; self._scenario_step = 0 - self._targets = [90.0]*6; self._wp_idx = 0; self._gripper = False; self._phase = 'idle' - - def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, ProximitySensor - from eosim.engine.native.peripherals.actuators import ServoController, MotorController - self.vm.add_peripheral('servo0', ServoController('servo0', 0x40200100, 6)) - self.vm.add_peripheral('motor0', MotorController('motor0', 0x40200000)) - self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200)) - self.vm.add_peripheral('prox0', ProximitySensor('prox0', 0x40100400)) - self.state = {'joint_angles': [90.0]*6, 'obstacle_dist_cm': 400, 'mode': 'IDLE', 'scenario': ''} - - def forward_kinematics(self, angles): - x, z, c = 0.0, 0.0, 0.0 - for a, l in zip(angles, self.LINK_LENGTHS): - c += math.radians(a - 90); x += l * math.cos(c); z += l * math.sin(c) - return [round(x, 4), 0, round(z, 4)] - - def load_scenario(self, name): - if name not in self.SCENARIOS: return - self.scenario = name; self._scenario_step = 0; self._wp_idx = 0; self.state['scenario'] = name; self._phase = 'start' - if name == 'home_calibration': self._targets = [90.0]*6; self.state['mode'] = 'CALIBRATING' - elif name == 'pick_and_place': self._targets = list(self.SCENARIOS[name]['pick']); self._phase = 'pick'; self.state['mode'] = 'RUNNING' - elif name == 'path_planning': self._targets = list(self.SCENARIOS[name]['waypoints'][0]); self.state['mode'] = 'RUNNING' - elif name == 'obstacle_avoidance': self.state['mode'] = 'RUNNING' - elif name == 'force_assembly': self._phase = 'approach'; self.state['mode'] = 'RUNNING' - - def _at_target(self, servo): - return all(abs(servo.positions[i] - self._targets[i]) < 2 for i in range(min(6, servo.channels))) - - def tick(self): - self.tick_count += 1 - for n, d in self.vm.peripherals.items(): - if hasattr(d, 'simulate_tick'): d.simulate_tick() - servo = self.vm.peripherals.get('servo0') - prox = self.vm.peripherals.get('prox0') - if self.scenario == 'pick_and_place' and servo: - cfg = self.SCENARIOS['pick_and_place'] - if self._phase == 'pick': self._targets = list(cfg['pick']); (self._gripper, self._phase) = (True, 'place') if self._at_target(servo) else (self._gripper, self._phase) - elif self._phase == 'place': self._targets = list(cfg['place']); (self._gripper, self._phase, self.state['mode']) = (False, 'done', 'IDLE') if self._at_target(servo) else (self._gripper, self._phase, self.state['mode']) - elif self.scenario == 'path_planning' and servo: - wps = self.SCENARIOS['path_planning']['waypoints'] - if self._wp_idx < len(wps): self._targets = list(wps[self._wp_idx]); self._wp_idx += 1 if self._at_target(servo) else 0 - else: self.state['mode'] = 'IDLE' - elif self.scenario == 'obstacle_avoidance' and prox: - self.state['mode'] = 'E_STOP' if prox.distance_cm < self.SCENARIOS['obstacle_avoidance']['safe_cm'] else 'RUNNING' - elif self.scenario == 'force_assembly' and self._phase == 'approach': - self._targets[4] -= 0.5 - if abs(self._targets[4] - 90) * 0.1 >= self.SCENARIOS['force_assembly']['target_n']: self._phase = 'hold'; self.state['mode'] = 'IDLE' - elif self.scenario == 'home_calibration' and servo and self._at_target(servo): self.state['mode'] = 'IDLE' - if servo: - for i in range(min(6, servo.channels)): servo.set_target(i, self._targets[i]) - self.state['joint_angles'] = [round(p, 1) for p in servo.positions[:6]] - if prox: self.state['obstacle_dist_cm'] = round(prox.distance_cm, 1) - self._scenario_step += 1 - - def get_state(self): return dict(self.state) - def get_peripherals(self): return dict(self.vm.peripherals) - def get_status_text(self): return f"{self.DISPLAY_NAME} | {self.state.get('mode','IDLE')} | Tick {self.tick_count}" - def reset(self): self.tick_count = 0; self.state = {}; self.scenario = ''; self._targets = [90.0]*6; self._wp_idx = 0 -# SPDX-License-Identifier: MIT -# Copyright (c) 2026 EoS Project """Robot simulator — 6-axis arm / mobile robot with kinematics. Pure Python, cross-platform (Linux/Windows/macOS). No OS-specific dependencies. """ import math -import random class RobotSimulator: @@ -131,8 +50,8 @@ def __init__(self, vm): self._phase = 'idle' def setup(self): + from eosim.engine.native.peripherals.actuators import MotorController, ServoController from eosim.engine.native.peripherals.sensors import IMUSensor, ProximitySensor - from eosim.engine.native.peripherals.actuators import ServoController, MotorController self.vm.add_peripheral('servo0', ServoController('servo0', 0x40200100, 6)) self.vm.add_peripheral('motor0', MotorController('motor0', 0x40200000)) @@ -192,7 +111,7 @@ def _at_target(self, servo) -> bool: def tick(self): self.tick_count += 1 - for name, dev in self.vm.peripherals.items(): + for _, dev in self.vm.peripherals.items(): if hasattr(dev, 'simulate_tick'): dev.simulate_tick() diff --git a/eosim/engine/native/simulators/satellite.py b/eosim/engine/native/simulators/satellite.py index d95f2a6..f3c7f48 100644 --- a/eosim/engine/native/simulators/satellite.py +++ b/eosim/engine/native/simulators/satellite.py @@ -4,91 +4,6 @@ import random -class SatelliteSimulator: - PRODUCT_TYPE = 'satellite' - DISPLAY_NAME = 'CubeSat / Satellite' - MODES = ['SAFE', 'DETUMBLE', 'SUN_POINTING', 'NOMINAL', 'COMMS', 'ECLIPSE'] - SCENARIOS = { - 'deployment': {'description': 'Post-deploy detumble'}, - 'detumble': {'description': 'Reduce angular rates'}, - 'sun_pointing': {'description': 'Orient panels to sun'}, - 'ground_pass': {'description': 'Downlink during ground pass'}, - 'eclipse_power': {'description': 'Eclipse power management'}, - 'safe_mode': {'description': 'Enter safe mode'}, - } - - def __init__(self, vm): - self.vm = vm; self.tick_count = 0; self.state = {} - self.scenario = ''; self._scenario_step = 0 - - def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, ADCChannel - from eosim.engine.native.peripherals.actuators import MotorController - from eosim.engine.native.peripherals.wireless import RFTransceiver - from eosim.engine.native.peripherals.composites import BatteryManagement, CryptoEngine, WatchdogTimer - self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200, 9)) - self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) - self.vm.add_peripheral('solar_adc', ADCChannel('solar_adc', 0x40100600, 4)) - self.vm.add_peripheral('reaction_wheel', MotorController('reaction_wheel', 0x40200000)) - self.vm.add_peripheral('rf0', RFTransceiver('rf0', 0x40400400)) - self.vm.add_peripheral('bms0', BatteryManagement('bms0', 0x40500000, 4, 2600)) - self.vm.add_peripheral('crypto0', CryptoEngine('crypto0', 0x40500300)) - self.vm.add_peripheral('wdt0', WatchdogTimer('wdt0', 0x40500200)) - self.state = {'orbit_alt_km': 550, 'angular_rate': [5, 3, 2], 'in_eclipse': False, - 'solar_power_w': 5.0, 'soc_pct': 90, 'ground_contact': False, - 'mode': 'DETUMBLE', 'data_downlinked_kb': 0, 'scenario': ''} - - def load_scenario(self, name): - if name not in self.SCENARIOS: return - self.scenario = name; self._scenario_step = 0; self.state['scenario'] = name - if name == 'deployment': self.state['mode'] = 'DETUMBLE'; self.state['angular_rate'] = [8, 6, 4] - elif name == 'detumble': self.state['mode'] = 'DETUMBLE' - elif name == 'sun_pointing': self.state['mode'] = 'SUN_POINTING' - elif name == 'ground_pass': self.state['mode'] = 'COMMS'; self.state['ground_contact'] = True - elif name == 'eclipse_power': self.state['mode'] = 'ECLIPSE'; self.state['in_eclipse'] = True - elif name == 'safe_mode': self.state['mode'] = 'SAFE' - - def tick(self): - self.tick_count += 1 - for n, d in self.vm.peripherals.items(): - if hasattr(d, 'simulate_tick'): d.simulate_tick() - bms = self.vm.peripherals.get('bms0'); imu = self.vm.peripherals.get('imu0') - wheel = self.vm.peripherals.get('reaction_wheel') - period = self.tick_count % 5400 - self.state['in_eclipse'] = period > 3600 - power = 0 if self.state['in_eclipse'] else 5.0 + random.gauss(0, 0.2) - self.state['solar_power_w'] = round(max(0, power), 2) - self.state['ground_contact'] = (period % 1800) < 600 - mode = self.state['mode'] - if mode == 'DETUMBLE': - for i in range(3): self.state['angular_rate'][i] *= 0.98 - if all(abs(r) < 0.5 for r in self.state['angular_rate']): self.state['mode'] = 'NOMINAL' - if wheel: wheel.enabled = True; wheel.target_speed = 100 - elif mode == 'COMMS': - if self.state['ground_contact']: self.state['data_downlinked_kb'] += 10 - else: self.state['mode'] = 'NOMINAL' - elif mode == 'ECLIPSE': - if not self.state['in_eclipse']: self.state['mode'] = 'NOMINAL' - elif mode == 'SAFE': - if wheel: wheel.enabled = False - if bms and bms.soc_percent > 50: self.state['mode'] = 'NOMINAL' - if bms: bms.current_ma = 800 if self.state['in_eclipse'] else 300; self.state['soc_pct'] = round(bms.soc_percent, 1) - if imu: imu.set_gyro(*self.state['angular_rate']) - self._scenario_step += 1 - - def get_state(self): return dict(self.state) - def get_peripherals(self): return dict(self.vm.peripherals) - def get_status_text(self): - ecl = ' [ECLIPSE]' if self.state.get('in_eclipse') else '' - return f"{self.DISPLAY_NAME} | {self.state.get('mode','SAFE')}{ecl} | Tick {self.tick_count}" - def reset(self): self.tick_count = 0; self.state = {}; self.scenario = '' -# SPDX-License-Identifier: MIT -# Copyright (c) 2026 EoS Project -"""Satellite / CubeSat simulator. Pure Python, cross-platform.""" -import math -import random - - class SatelliteSimulator: PRODUCT_TYPE = 'satellite' DISPLAY_NAME = 'CubeSat / Satellite' @@ -112,10 +27,14 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, ADCChannel from eosim.engine.native.peripherals.actuators import MotorController + from eosim.engine.native.peripherals.composites import ( + BatteryManagement, + CryptoEngine, + WatchdogTimer, + ) + from eosim.engine.native.peripherals.sensors import ADCChannel, GPSModule, IMUSensor from eosim.engine.native.peripherals.wireless import RFTransceiver - from eosim.engine.native.peripherals.composites import BatteryManagement, CryptoEngine, WatchdogTimer self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200, 9)) self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) @@ -162,7 +81,7 @@ def load_scenario(self, name: str): def tick(self): self.tick_count += 1 - for name, dev in self.vm.peripherals.items(): + for _, dev in self.vm.peripherals.items(): if hasattr(dev, 'simulate_tick'): dev.simulate_tick() diff --git a/eosim/engine/native/simulators/speaker.py b/eosim/engine/native/simulators/speaker.py index 509c74c..f1886f6 100644 --- a/eosim/engine/native/simulators/speaker.py +++ b/eosim/engine/native/simulators/speaker.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Smart Speaker simulator. Pure Python, cross-platform.""" -import random class SmartSpeakerSimulator: @@ -25,9 +24,9 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.wireless import WiFiModule, BLEModule - from eosim.engine.native.peripherals.sensors import TemperatureSensor from eosim.engine.native.peripherals.composites import BatteryManagement + from eosim.engine.native.peripherals.sensors import TemperatureSensor + from eosim.engine.native.peripherals.wireless import BLEModule, WiFiModule self.vm.add_peripheral('wifi0', WiFiModule('wifi0', 0x40400000)) self.vm.add_peripheral('ble0', BLEModule('ble0', 0x40400100)) diff --git a/eosim/engine/native/simulators/vehicle.py b/eosim/engine/native/simulators/vehicle.py index 4b9eeca..dd53711 100644 --- a/eosim/engine/native/simulators/vehicle.py +++ b/eosim/engine/native/simulators/vehicle.py @@ -39,11 +39,15 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, GPSModule, TemperatureSensor from eosim.engine.native.peripherals.actuators import ( - MotorController, SteeringActuator, ThrottleActuator, BrakeActuator) + BrakeActuator, + MotorController, + SteeringActuator, + ThrottleActuator, + ) from eosim.engine.native.peripherals.buses import CANBusController, LINBusController from eosim.engine.native.peripherals.composites import BatteryManagement, WatchdogTimer + from eosim.engine.native.peripherals.sensors import GPSModule, IMUSensor, TemperatureSensor self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200)) self.vm.add_peripheral('gps0', GPSModule('gps0', 0x40100300)) @@ -85,7 +89,7 @@ def tick(self): throttle = self.vm.peripherals.get('throttle') brake = self.vm.peripherals.get('brake') - motor = self.vm.peripherals.get('motor0') + self.vm.peripherals.get('motor0') bms = self.vm.peripherals.get('bms0') gps = self.vm.peripherals.get('gps0') steering = self.vm.peripherals.get('steering') diff --git a/eosim/engine/native/simulators/wearable.py b/eosim/engine/native/simulators/wearable.py index c76358a..6b710fa 100644 --- a/eosim/engine/native/simulators/wearable.py +++ b/eosim/engine/native/simulators/wearable.py @@ -26,10 +26,14 @@ def __init__(self, vm): self._step_acc = 0.0 def setup(self): - from eosim.engine.native.peripherals.sensors import IMUSensor, PulseOximeter, TemperatureSensor from eosim.engine.native.peripherals.actuators import DisplayDriver, HapticDriver - from eosim.engine.native.peripherals.wireless import BLEModule from eosim.engine.native.peripherals.composites import BatteryManagement + from eosim.engine.native.peripherals.sensors import ( + IMUSensor, + PulseOximeter, + TemperatureSensor, + ) + from eosim.engine.native.peripherals.wireless import BLEModule self.vm.add_peripheral('imu0', IMUSensor('imu0', 0x40100200)) self.vm.add_peripheral('spo2_0', PulseOximeter('spo2_0', 0x40100900)) diff --git a/eosim/engine/native/simulators/weather.py b/eosim/engine/native/simulators/weather.py index 9825c64..745a7e1 100644 --- a/eosim/engine/native/simulators/weather.py +++ b/eosim/engine/native/simulators/weather.py @@ -2,7 +2,6 @@ # Copyright (c) 2026 EoS Project """Weather simulator — atmospheric pressure, temperature, precipitation.""" import math -import random class WeatherSimulator: @@ -40,9 +39,12 @@ def __init__(self, vm): self._scenario_step = 0 def setup(self): - from eosim.engine.native.peripherals.sensors_weather import ( - WeatherStation, Anemometer, RadarSensor) from eosim.engine.native.peripherals.actuators_weather import WeatherActuator + from eosim.engine.native.peripherals.sensors_weather import ( + Anemometer, + RadarSensor, + WeatherStation, + ) self.vm.add_peripheral('station', WeatherStation('station', 0x40140000)) self.vm.add_peripheral('anemometer', Anemometer('anemometer', 0x40140100)) self.vm.add_peripheral('radar', RadarSensor('radar', 0x40140200)) @@ -97,7 +99,7 @@ def get_peripherals(self): return dict(self.vm.peripherals) def get_status_text(self): - scn = " [%s]" % self.scenario if self.scenario else "" + scn = f" [{self.scenario}]" if self.scenario else "" return "%s | Tick %d%s" % (self.DISPLAY_NAME, self.tick_count, scn) def reset(self): diff --git a/eosim/engine/peripherals.py b/eosim/engine/peripherals.py index a723ef7..614e3a1 100644 --- a/eosim/engine/peripherals.py +++ b/eosim/engine/peripherals.py @@ -50,10 +50,10 @@ def generate_repl_peripherals(peripherals: list) -> str: for pname in peripherals: model = PERIPHERAL_MODELS.get(pname) if model: - lines.append("// %s — %s" % (pname, model["description"])) - lines.append("%s: %s @ sysbus" % (pname, model["renode_type"])) + lines.append("// {} — {}".format(pname, model["description"])) + lines.append("{}: {} @ sysbus".format(pname, model["renode_type"])) lines.append("") return "\n".join(lines) def list_peripherals() -> list: - return list(PERIPHERAL_MODELS.keys()) \ No newline at end of file + return list(PERIPHERAL_MODELS.keys()) diff --git a/eosim/engine/qemu/elf_loader.py b/eosim/engine/qemu/elf_loader.py index 68de863..5ca84bf 100644 --- a/eosim/engine/qemu/elf_loader.py +++ b/eosim/engine/qemu/elf_loader.py @@ -6,7 +6,7 @@ Optionally uses pyelftools if installed for richer symbol info. """ import struct -from typing import List, Dict, Optional, NamedTuple +from typing import NamedTuple class ELFSegment(NamedTuple): @@ -36,12 +36,12 @@ def __init__(self): self.arch: str = '' self.bits: int = 32 self.endian: str = 'little' - self.segments: List[ELFSegment] = [] - self.symbols: List[ELFSymbol] = [] - self.sections: Dict[str, dict] = {} + self.segments: list[ELFSegment] = [] + self.symbols: list[ELFSymbol] = [] + self.sections: dict[str, dict] = {} @property - def load_segments(self) -> List[ELFSegment]: + def load_segments(self) -> list[ELFSegment]: """Return only PT_LOAD segments.""" return [s for s in self.segments if s.type == 1] diff --git a/eosim/engine/qemu/gdb_client.py b/eosim/engine/qemu/gdb_client.py index 2dbcbf1..c6ef8b6 100644 --- a/eosim/engine/qemu/gdb_client.py +++ b/eosim/engine/qemu/gdb_client.py @@ -6,8 +6,7 @@ and execution control. Works with any GDB server (QEMU -gdb, OpenOCD, etc.). """ import socket -import re -from typing import Optional, List, Dict +from typing import Optional class GDBError(Exception): @@ -91,7 +90,7 @@ def _command(self, cmd: str) -> str: # --- Register access --- - def read_all_registers(self) -> Dict[str, int]: + def read_all_registers(self) -> dict[str, int]: """Read all CPU registers. Returns dict of name→value.""" resp = self._command('g') if not resp or resp.startswith('E'): @@ -182,7 +181,7 @@ def halt(self) -> str: # --- Target info --- - def get_thread_info(self) -> List[str]: + def get_thread_info(self) -> list[str]: """Get thread/CPU list.""" threads = [] resp = self._command('qfThreadInfo') @@ -230,7 +229,7 @@ def arch(self) -> str: return self._arch @property - def register_names(self) -> List[str]: + def register_names(self) -> list[str]: return list(self._reg_names) def disconnect(self): diff --git a/eosim/engine/qemu/qmp_client.py b/eosim/engine/qemu/qmp_client.py index aa294de..96fccb0 100644 --- a/eosim/engine/qemu/qmp_client.py +++ b/eosim/engine/qemu/qmp_client.py @@ -9,7 +9,7 @@ import socket import threading import time -from typing import Optional, Dict, Any, Callable, List +from typing import Any, Callable, Optional class QMPError(Exception): @@ -27,7 +27,7 @@ def __init__(self): self._sock: Optional[socket.socket] = None self._connected = False self._event_thread: Optional[threading.Thread] = None - self._event_handlers: Dict[str, List[Callable]] = {} + self._event_handlers: dict[str, list[Callable]] = {} self._lock = threading.Lock() self._recv_buffer = b'' @@ -55,7 +55,7 @@ def _negotiate(self): raise QMPError(f"Capability negotiation failed: {resp}") self._connected = True - def execute(self, command: str, arguments: Dict[str, Any] = None) -> dict: + def execute(self, command: str, arguments: dict[str, Any] = None) -> dict: """Execute a QMP command and return the response.""" msg = {'execute': command} if arguments: diff --git a/eosim/engine/qemu/state_bridge.py b/eosim/engine/qemu/state_bridge.py index 68b5a35..a09f334 100644 --- a/eosim/engine/qemu/state_bridge.py +++ b/eosim/engine/qemu/state_bridge.py @@ -7,7 +7,7 @@ """ import threading import time -from typing import Optional, Callable +from typing import Callable, Optional class TargetStateBridge: diff --git a/eosim/gui/product_templates.py b/eosim/gui/product_templates.py index 614d222..a1f4b47 100644 --- a/eosim/gui/product_templates.py +++ b/eosim/gui/product_templates.py @@ -2,7 +2,7 @@ # Copyright (c) 2026 EoS Project """Product template definitions for EoSim simulation UI.""" from dataclasses import dataclass, field -from typing import List, Dict, Optional +from typing import Optional @dataclass @@ -12,7 +12,7 @@ class ProductTemplate: icon: str = "" arch: str = "arm" ram_mb: int = 128 - peripherals: List[str] = field(default_factory=list) + peripherals: list[str] = field(default_factory=list) domain: str = "" modeling: str = "deterministic" description: str = "" @@ -20,7 +20,7 @@ class ProductTemplate: simulator_class: str = "" -PRODUCT_CATALOG: Dict[str, ProductTemplate] = { +PRODUCT_CATALOG: dict[str, ProductTemplate] = { "iot_sensor": ProductTemplate( name="iot_sensor", display_name="IoT Sensor", @@ -431,5 +431,5 @@ def get_template(name: str) -> Optional[ProductTemplate]: return PRODUCT_CATALOG.get(name) -def list_templates() -> List[str]: +def list_templates() -> list[str]: return sorted(PRODUCT_CATALOG.keys()) diff --git a/eosim/gui/renderers/__init__.py b/eosim/gui/renderers/__init__.py index 1bbf2a4..71bb383 100644 --- a/eosim/gui/renderers/__init__.py +++ b/eosim/gui/renderers/__init__.py @@ -2,10 +2,11 @@ # Copyright (c) 2026 EoS Project """3D renderer registry — base class and domain-specific renderer lookup.""" from __future__ import annotations + from abc import ABC, abstractmethod from typing import Dict, Type -_REGISTRY: Dict[str, "BaseRenderer"] = {} +_REGISTRY: dict[str, BaseRenderer] = {} class BaseRenderer(ABC): @@ -54,7 +55,7 @@ def update(self, ax, state: dict): _FALLBACK = _FallbackRenderer() -def register_renderer(domain: str, cls: Type[BaseRenderer]) -> None: +def register_renderer(domain: str, cls: type[BaseRenderer]) -> None: """Register a renderer class for *domain*.""" _REGISTRY[domain] = cls() diff --git a/eosim/gui/renderers/aerodynamics.py b/eosim/gui/renderers/aerodynamics.py index 186ee49..e01c58a 100644 --- a/eosim/gui/renderers/aerodynamics.py +++ b/eosim/gui/renderers/aerodynamics.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """3D renderer for aerodynamics (domain: aerodynamics).""" -import math from eosim.gui.renderers import BaseRenderer, register_renderer @@ -24,7 +23,7 @@ def update(self, ax, state): cd = state.get("cd", 0) airspeed = state.get("airspeed_mps", 0) mach = state.get("mach_number", 0) - ax.set_title("Aero V=%.0fm/s M=%.3f AoA=%.1f Cl=%.3f Cd=%.4f" % (airspeed, mach, aoa, cl, cd), fontsize=8) + ax.set_title(f"Aero V={airspeed:.0f}m/s M={mach:.3f} AoA={aoa:.1f} Cl={cl:.3f} Cd={cd:.4f}", fontsize=8) register_renderer("aerodynamics", AerodynamicsRenderer) diff --git a/eosim/gui/renderers/consumer.py b/eosim/gui/renderers/consumer.py index 51ac324..f3bc625 100644 --- a/eosim/gui/renderers/consumer.py +++ b/eosim/gui/renderers/consumer.py @@ -2,6 +2,7 @@ # Copyright (c) 2026 EoS Project """Consumer Electronics 3D renderer — bar chart of key media/speaker/camera state values.""" import math + from eosim.gui.renderers import BaseRenderer, register_renderer diff --git a/eosim/gui/renderers/finance.py b/eosim/gui/renderers/finance.py index 76fe6ab..861f0d5 100644 --- a/eosim/gui/renderers/finance.py +++ b/eosim/gui/renderers/finance.py @@ -21,7 +21,7 @@ def setup(self, ax): def update(self, ax, state): price = state.get("price", 100) pnl = state.get("pnl", 0) - ax.set_title("Market $%.2f PnL=$%.2f" % (price, pnl), fontsize=8) + ax.set_title(f"Market ${price:.2f} PnL=${pnl:.2f}", fontsize=8) register_renderer("finance", FinanceRenderer) diff --git a/eosim/gui/renderers/generic.py b/eosim/gui/renderers/generic.py index b043a8e..3f96408 100644 --- a/eosim/gui/renderers/generic.py +++ b/eosim/gui/renderers/generic.py @@ -29,6 +29,7 @@ def update(self, ax, state: dict): # Copyright (c) 2026 EoS Project """Generic 3D renderer — fallback bar chart of all numeric state values.""" import math + from eosim.gui.renderers import BaseRenderer, register_renderer diff --git a/eosim/gui/renderers/physiology.py b/eosim/gui/renderers/physiology.py index 05c3022..fd7ffc5 100644 --- a/eosim/gui/renderers/physiology.py +++ b/eosim/gui/renderers/physiology.py @@ -2,6 +2,7 @@ # Copyright (c) 2026 EoS Project """3D renderer for patient physiology (domain: physiology).""" import math + from eosim.gui.renderers import BaseRenderer, register_renderer @@ -62,7 +63,6 @@ def update(self, ax, state: dict): f"BP: {bp_sys:.0f}/{bp_dia:.0f}", f"RR: {rr:.0f}", f"Temp: {temp:.1f}°C", ] - y_offset = 1.8 for i, txt in enumerate(vitals): ax.text(-1.8, -1.5, 2.5 - i * 0.3, txt, fontsize=7, color="#cccccc") diff --git a/eosim/gui/renderers/weather.py b/eosim/gui/renderers/weather.py index fbe68f5..2248e2b 100644 --- a/eosim/gui/renderers/weather.py +++ b/eosim/gui/renderers/weather.py @@ -3,6 +3,7 @@ """3D renderer for weather systems (domain: weather).""" import math import random + from eosim.gui.renderers import BaseRenderer, register_renderer @@ -63,7 +64,7 @@ def update(self, ax, state: dict): wind_dir = state.get("wind_direction_deg", 180) precip = state.get("precipitation_mm_hr", 0) cloud_cover = state.get("cloud_cover_pct", 30) - vis = state.get("visibility_km", 10) + state.get("visibility_km", 10) self._draw_wind_vectors(ax, wind_speed, wind_dir) self._draw_clouds(ax, cloud_cover) diff --git a/eosim/gui/simulator_app.py b/eosim/gui/simulator_app.py index ddb16d9..8591277 100644 --- a/eosim/gui/simulator_app.py +++ b/eosim/gui/simulator_app.py @@ -7,8 +7,8 @@ simulation ticking, and panel updates. """ from eosim.engine.native import VirtualMachine -from eosim.engine.native.simulators import SimulatorFactory, BaseSimulator -from eosim.gui.product_templates import PRODUCT_CATALOG, get_template +from eosim.engine.native.simulators import BaseSimulator, SimulatorFactory +from eosim.gui.product_templates import get_template class SimulatorApp: diff --git a/eosim/gui/tk_app.py b/eosim/gui/tk_app.py index 5df0e65..66d11a4 100644 --- a/eosim/gui/tk_app.py +++ b/eosim/gui/tk_app.py @@ -9,14 +9,14 @@ import tkinter as tk from tkinter import ttk +from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.gui.simulator_app import SimulatorApp -from eosim.gui.widgets.build_panel import BuildPanel, PERIPHERAL_CATEGORIES -from eosim.gui.widgets.peripheral_panel import PeripheralPanel -from eosim.gui.widgets.uart_terminal import UARTTerminal +from eosim.gui.widgets.build_panel import PERIPHERAL_CATEGORIES, BuildPanel from eosim.gui.widgets.cpu_panel import CPUPanel from eosim.gui.widgets.gpio_panel import GPIOPanel from eosim.gui.widgets.memory_view import MemoryView -from eosim.gui.product_templates import PRODUCT_CATALOG +from eosim.gui.widgets.peripheral_panel import PeripheralPanel +from eosim.gui.widgets.uart_terminal import UARTTerminal from eosim.gui.widgets.viewer_3d import Viewer3DPanel diff --git a/eosim/gui/widgets/build_panel.py b/eosim/gui/widgets/build_panel.py index f72b40f..4e0a6ba 100644 --- a/eosim/gui/widgets/build_panel.py +++ b/eosim/gui/widgets/build_panel.py @@ -5,9 +5,8 @@ Expands peripheral checkboxes to include all device types, grouped by category. Auto-checks peripherals from product template. """ -from typing import Dict, List, Set -from eosim.gui.product_templates import PRODUCT_CATALOG, get_template +from eosim.gui.product_templates import PRODUCT_CATALOG, get_template PERIPHERAL_CATEGORIES = { 'Core': ['uart', 'gpio', 'timer', 'spi', 'i2c', 'nvic'], @@ -36,7 +35,7 @@ ], } -ALL_PERIPHERALS: Set[str] = set() +ALL_PERIPHERALS: set[str] = set() for periphs in PERIPHERAL_CATEGORIES.values(): ALL_PERIPHERALS.update(periphs) @@ -48,7 +47,7 @@ def __init__(self): self.product_type: str = '' self.arch: str = 'arm' self.ram_mb: int = 128 - self.selected_peripherals: Set[str] = set() + self.selected_peripherals: set[str] = set() self.simulator_class: str = '' def load_from_template(self, product_name: str): @@ -97,7 +96,7 @@ def select_product(self, product_name: str) -> bool: """Select a product template and auto-check its peripherals.""" return self.config.load_from_template(product_name) - def get_peripheral_groups(self) -> Dict[str, List[dict]]: + def get_peripheral_groups(self) -> dict[str, list[dict]]: """Return peripheral groups with checked state for UI rendering.""" groups = {} for category, periphs in self.categories.items(): @@ -119,7 +118,7 @@ def get_build_config(self) -> dict: """Get the current build configuration.""" return self.config.to_dict() - def list_products(self) -> List[dict]: + def list_products(self) -> list[dict]: """Return list of all product templates for selection UI.""" products = [] for key in sorted(PRODUCT_CATALOG.keys()): diff --git a/eosim/gui/widgets/peripheral_panel.py b/eosim/gui/widgets/peripheral_panel.py index 5e90e7d..1e06f7b 100644 --- a/eosim/gui/widgets/peripheral_panel.py +++ b/eosim/gui/widgets/peripheral_panel.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Peripheral display panel — dynamically renders domain-specific peripherals.""" -from typing import Dict, List, Optional class PeripheralSubPanel: diff --git a/eosim/gui/widgets/viewer_3d.py b/eosim/gui/widgets/viewer_3d.py index 93b356b..8b4ea85 100644 --- a/eosim/gui/widgets/viewer_3d.py +++ b/eosim/gui/widgets/viewer_3d.py @@ -7,8 +7,8 @@ try: import matplotlib matplotlib.use('TkAgg') - from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + from matplotlib.figure import Figure from mpl_toolkits.mplot3d import Axes3D # noqa: F401 HAS_MATPLOTLIB = True except ImportError: diff --git a/eosim/integrations/__init__.py b/eosim/integrations/__init__.py index df16021..dea5682 100644 --- a/eosim/integrations/__init__.py +++ b/eosim/integrations/__init__.py @@ -1,8 +1,14 @@ # SPDX-License-Identifier: MIT -from eosim.integrations.eos_runner import ( # noqa: F401 - find_eos_source, build_eos, run_eos_tests, - run_eosuite_tests, EosTestSuite, EosTestResult -) from eosim.integrations.ecosystem import ( # noqa: F401 - run_ecosystem_tests, find_repos, EcosystemReport + EcosystemReport, + find_repos, + run_ecosystem_tests, +) +from eosim.integrations.eos_runner import ( # noqa: F401 + EosTestResult, + EosTestSuite, + build_eos, + find_eos_source, + run_eos_tests, + run_eosuite_tests, ) diff --git a/eosim/integrations/ecosystem.py b/eosim/integrations/ecosystem.py index 8b77493..b003d2d 100644 --- a/eosim/integrations/ecosystem.py +++ b/eosim/integrations/ecosystem.py @@ -2,12 +2,11 @@ # Copyright (c) 2026 EoS Project """EoS Ecosystem Runner — test all repos through EoSim.""" import os +import shutil +import subprocess import sys import time -import subprocess -import shutil from dataclasses import dataclass, field -from typing import List, Dict @dataclass @@ -32,8 +31,8 @@ class EcosystemReport: total_passed: int = 0 total_failed: int = 0 duration_s: float = 0.0 - results: List[RepoTestResult] = field(default_factory=list) - simulations: List[dict] = field(default_factory=list) + results: list[RepoTestResult] = field(default_factory=list) + simulations: list[dict] = field(default_factory=list) def summary(self) -> str: lines = [] @@ -45,7 +44,7 @@ def summary(self) -> str: self.repos_tested, self.repos_passed, self.repos_failed)) lines.append(" Tests: %d total | %d passed | %d failed" % ( self.total_tests, self.total_passed, self.total_failed)) - lines.append(" Time: %.2fs" % self.duration_s) + lines.append(f" Time: {self.duration_s:.2f}s") lines.append("") for r in self.results: status = "PASS" if r.passed else "FAIL" @@ -71,7 +70,7 @@ def summary(self) -> str: return "\n".join(lines) -def find_repos(workspace: str = None) -> Dict[str, str]: +def find_repos(workspace: str = None) -> dict[str, str]: if not workspace: workspace = os.environ.get("EOS_WORKSPACE", "") if not workspace: diff --git a/eosim/integrations/eos_runner.py b/eosim/integrations/eos_runner.py index 3e36e8b..3889bc4 100644 --- a/eosim/integrations/eos_runner.py +++ b/eosim/integrations/eos_runner.py @@ -2,12 +2,12 @@ # Copyright (c) 2026 EoS Project """EoS Integration — build and test EoS apps through EoSim.""" import os -import sys -import subprocess import shutil +import subprocess +import sys import time -from typing import Optional, List from dataclasses import dataclass, field +from typing import Optional @dataclass @@ -26,15 +26,15 @@ class EosTestSuite: passed: int = 0 failed: int = 0 skipped: int = 0 - results: List[EosTestResult] = field(default_factory=list) + results: list[EosTestResult] = field(default_factory=list) duration_s: float = 0.0 build_log: str = "" def summary(self) -> str: - lines = ["EoSim Test Suite: %s" % self.platform] + lines = [f"EoSim Test Suite: {self.platform}"] lines.append(" Total: %d | Passed: %d | Failed: %d | Skipped: %d" % ( self.total, self.passed, self.failed, self.skipped)) - lines.append(" Duration: %.2fs" % self.duration_s) + lines.append(f" Duration: {self.duration_s:.2f}s") lines.append("") for r in self.results: status = "PASS" if r.passed else "FAIL" @@ -104,7 +104,7 @@ def build_eos(source_dir: str, build_dir: str = None, log_lines.append(r.stderr) return False, "\n".join(log_lines) except (subprocess.TimeoutExpired, FileNotFoundError) as e: - return False, "Build configure failed: %s" % str(e) + return False, f"Build configure failed: {str(e)}" # Build build_cmd = [cmake, "--build", build_dir] @@ -120,7 +120,7 @@ def build_eos(source_dir: str, build_dir: str = None, log_lines.append(r.stderr) return False, "\n".join(log_lines) except (subprocess.TimeoutExpired, FileNotFoundError) as e: - return False, "Build failed: %s" % str(e) + return False, f"Build failed: {str(e)}" return True, "\n".join(log_lines) diff --git a/eosim/integrations/gazebo.py b/eosim/integrations/gazebo.py index a9dcb87..ca3938d 100644 --- a/eosim/integrations/gazebo.py +++ b/eosim/integrations/gazebo.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Gazebo simulation integration — model spawn, state, force control.""" -import subprocess import shutil -from typing import Optional, Dict +import subprocess +from typing import Optional class GazeboConnection: @@ -17,7 +17,7 @@ def __init__(self, host: str = '127.0.0.1', port: int = 11345): self.port = port self.connected = False self._process: Optional[subprocess.Popen] = None - self._models: Dict[str, dict] = {} + self._models: dict[str, dict] = {} @staticmethod def available() -> bool: diff --git a/eosim/integrations/hil_session.py b/eosim/integrations/hil_session.py index dfd16d0..b8cedab 100644 --- a/eosim/integrations/hil_session.py +++ b/eosim/integrations/hil_session.py @@ -26,10 +26,10 @@ def start(self, adapter: str = 'stlink', target: str = 'stm32f4', serial_port: str = '', baudrate: int = 115200, gdb_port: int = 3333, arch: str = 'arm'): """Start a full HIL session: OpenOCD → GDB → serial → state bridge.""" - from eosim.integrations.openocd import OpenOCDManager from eosim.engine.qemu.gdb_client import GDBRemoteClient - from eosim.integrations.serial_bridge import SerialBridge from eosim.engine.qemu.state_bridge import TargetStateBridge + from eosim.integrations.openocd import OpenOCDManager + from eosim.integrations.serial_bridge import SerialBridge self._config = { 'adapter': adapter, 'target': target, diff --git a/eosim/integrations/openfoam.py b/eosim/integrations/openfoam.py index 5de534b..b65eaaf 100644 --- a/eosim/integrations/openfoam.py +++ b/eosim/integrations/openfoam.py @@ -4,11 +4,11 @@ Case management, solver execution, result parsing. """ -import subprocess -import shutil import os import re -from typing import Optional, Dict, List +import shutil +import subprocess +from typing import Optional class OpenFOAMRunner: @@ -25,7 +25,7 @@ def __init__(self, case_dir: str = ''): self.case_dir = case_dir self.solver = 'simpleFoam' self._process: Optional[subprocess.Popen] = None - self._results: Dict[str, list] = {} + self._results: dict[str, list] = {} self._log: str = '' self._converged = False @@ -43,17 +43,17 @@ def set_solver(self, solver: str): if solver in self.SOLVERS: self.solver = solver - def validate_case(self) -> List[str]: + def validate_case(self) -> list[str]: errors = [] if not self.case_dir or not os.path.isdir(self.case_dir): - errors.append('Case directory does not exist: %s' % self.case_dir) + errors.append(f'Case directory does not exist: {self.case_dir}') return errors required = ['system/controlDict', 'system/fvSchemes', 'system/fvSolution', 'constant'] for req in required: path = os.path.join(self.case_dir, req) if not os.path.exists(path): - errors.append('Missing: %s' % req) + errors.append(f'Missing: {req}') return errors def run(self, timeout: int = 300) -> dict: @@ -72,7 +72,7 @@ def run(self, timeout: int = 300) -> dict: solver_path = shutil.which(self.solver) if not solver_path: - result['log'] = 'Solver not found: %s' % self.solver + result['log'] = f'Solver not found: {self.solver}' return result try: @@ -98,8 +98,8 @@ def run(self, timeout: int = 300) -> dict: return result - def parse_residuals(self) -> Dict[str, List[float]]: - residuals: Dict[str, List[float]] = {} + def parse_residuals(self) -> dict[str, list[float]]: + residuals: dict[str, list[float]] = {} pattern = re.compile( r'Solving for (\w+),.*Initial residual = ([0-9.e+-]+)') for line in self._log.split('\n'): @@ -128,7 +128,7 @@ def get_field_data(self, field: str, time_step: str = 'latest') -> dict: field_path = os.path.join(self.case_dir, time_step, field) if not os.path.exists(field_path): - return {'error': 'Field file not found: %s' % field_path} + return {'error': f'Field file not found: {field_path}'} return { 'field': field, diff --git a/eosim/integrations/openocd.py b/eosim/integrations/openocd.py index 74bd736..7c9d0aa 100644 --- a/eosim/integrations/openocd.py +++ b/eosim/integrations/openocd.py @@ -9,8 +9,7 @@ import shutil import subprocess import time -from typing import Optional, List - +from typing import Optional ADAPTER_CONFIGS = { 'stlink': 'interface/stlink.cfg', @@ -82,7 +81,7 @@ def available() -> bool: return bool(OpenOCDManager.find_openocd()) def launch(self, adapter: str = 'stlink', target: str = 'stm32f4', - gdb_port: int = 3333, extra_args: List[str] = None) -> bool: + gdb_port: int = 3333, extra_args: list[str] = None) -> bool: """Launch OpenOCD with the specified adapter and target config.""" openocd = self.find_openocd() if not openocd: diff --git a/eosim/integrations/serial_bridge.py b/eosim/integrations/serial_bridge.py index 6ba6ac2..7ec9b60 100644 --- a/eosim/integrations/serial_bridge.py +++ b/eosim/integrations/serial_bridge.py @@ -7,7 +7,7 @@ """ import threading import time -from typing import Optional, Callable, List +from typing import Callable, Optional try: import serial @@ -38,7 +38,7 @@ def available() -> bool: return HAS_SERIAL @staticmethod - def list_ports() -> List[dict]: + def list_ports() -> list[dict]: """Enumerate available serial ports.""" if not HAS_SERIAL: return [] @@ -55,7 +55,7 @@ def list_ports() -> List[dict]: return ports @staticmethod - def detect_dev_boards() -> List[dict]: + def detect_dev_boards() -> list[dict]: """Detect common development board serial adapters.""" if not HAS_SERIAL: return [] diff --git a/eosim/integrations/xplane.py b/eosim/integrations/xplane.py index d404b3c..d52c0b7 100644 --- a/eosim/integrations/xplane.py +++ b/eosim/integrations/xplane.py @@ -3,7 +3,7 @@ """X-Plane flight simulator integration — UDP dataref bridge.""" import socket import struct -from typing import Optional, Dict +from typing import Optional class XPlaneConnection: @@ -20,7 +20,7 @@ def __init__(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT): self.port = port self._sock: Optional[socket.socket] = None self.connected = False - self._datarefs: Dict[str, float] = {} + self._datarefs: dict[str, float] = {} def connect(self, timeout: float = 5.0) -> bool: try: @@ -29,7 +29,7 @@ def connect(self, timeout: float = 5.0) -> bool: self._sock.connect((self.host, self.port)) self.connected = True return True - except (socket.error, OSError): + except OSError: self.connected = False return False @@ -54,7 +54,7 @@ def set_dataref(self, path: str, value: float): msg += path.encode('ascii').ljust(500, b'\x00') self._sock.send(msg) self._datarefs[path] = value - except (socket.error, OSError): + except OSError: pass def set_position(self, lat: float, lon: float, alt_m: float, @@ -67,7 +67,7 @@ def set_position(self, lat: float, lon: float, alt_m: float, msg += struct.pack(' dict: @@ -85,7 +85,7 @@ def receive_data(self, timeout: float = 0.1) -> dict: result[idx] = values offset += 36 return result - except (socket.timeout, socket.error, OSError): + except (socket.timeout, OSError): pass return {} diff --git a/eosim/tests/__init__.py b/eosim/tests/__init__.py index c426840..0facd16 100644 --- a/eosim/tests/__init__.py +++ b/eosim/tests/__init__.py @@ -1,5 +1,3 @@ # SPDX-License-Identifier: MIT """Tests package.""" -from eosim.tests.runner import ( # noqa: F401 - run_checks, load_checks, CheckResult -) +from eosim.tests.runner import CheckResult, load_checks, run_checks # noqa: F401 diff --git a/eosim/tests/runner.py b/eosim/tests/runner.py index 4e1df6d..56ad142 100644 --- a/eosim/tests/runner.py +++ b/eosim/tests/runner.py @@ -1,10 +1,11 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Test runner — validates simulation output against checks.""" -import yaml import os from dataclasses import dataclass -from typing import List + +import yaml + from eosim.engine.backend import SimResult @@ -24,7 +25,7 @@ def load_checks(platform_dir: str) -> list: return data.get("checks", []) -def run_checks(sim_result: SimResult, checks: list) -> List[CheckResult]: +def run_checks(sim_result: SimResult, checks: list) -> list[CheckResult]: results = [] for check in checks: ctype = check.get("type", "") @@ -32,14 +33,14 @@ def run_checks(sim_result: SimResult, checks: list) -> List[CheckResult]: value = check.get("value", "") passed = value in sim_result.stdout results.append(CheckResult( - name="serial_contains: %s" % value, + name=f"serial_contains: {value}", passed=passed, message="found" if passed else "not found in output")) elif ctype == "exit_code": expected = check.get("value", 0) passed = sim_result.exit_code == int(expected) results.append(CheckResult( - name="exit_code == %s" % expected, + name=f"exit_code == {expected}", passed=passed, message="got %d" % sim_result.exit_code)) elif ctype == "timeout": @@ -48,7 +49,7 @@ def run_checks(sim_result: SimResult, checks: list) -> List[CheckResult]: results.append(CheckResult( name="timeout <= %ds" % seconds, passed=passed, - message="took %.1fs" % sim_result.duration_s)) + message=f"took {sim_result.duration_s:.1f}s")) elif ctype == "boot_success": passed = sim_result.boot_detected results.append( diff --git a/eosim/tests/scenarios.py b/eosim/tests/scenarios.py index 1820f6c..61ff0bb 100644 --- a/eosim/tests/scenarios.py +++ b/eosim/tests/scenarios.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Advanced test scenarios — boot, peripheral, networking.""" + import yaml -from typing import List + from eosim.tests.runner import CheckResult @@ -14,7 +15,7 @@ def load_scenario(path: str) -> dict: def run_scenario( scenario: dict, sim_stdout: str, - duration: float) -> List[CheckResult]: + duration: float) -> list[CheckResult]: results = [] for step in scenario.get("steps", []): stype = step.get("type", "") @@ -23,8 +24,7 @@ def run_scenario( passed = pattern in sim_stdout results.append( CheckResult( - name="wait_for: %s" % - pattern, + name=f"wait_for: {pattern}", passed=passed, message="found" if passed else "not found")) elif stype == "assert_no": @@ -32,8 +32,7 @@ def run_scenario( passed = pattern not in sim_stdout results.append( CheckResult( - name="assert_no: %s" % - pattern, + name=f"assert_no: {pattern}", passed=passed, message="absent" if passed else "found (unexpected)")) elif stype == "timing": @@ -44,8 +43,7 @@ def run_scenario( name="timing <= %ds" % max_s, passed=passed, - message="%.1fs" % - duration)) + message=f"{duration:.1f}s")) elif stype == "count_matches": pattern = step.get("pattern", "") expected = step.get("min_count", 1) diff --git a/tests/__init__.py b/tests/__init__.py index d589fc3..548d2d4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -# SPDX-License-Identifier: MIT \ No newline at end of file +# SPDX-License-Identifier: MIT diff --git a/tests/conftest.py b/tests/conftest.py index 38c4cf7..4fd0019 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,9 @@ """Shared test fixtures for EoSim test suite.""" -import os + import pytest import yaml -from pathlib import Path -from unittest.mock import MagicMock -from eosim.core.platform import Platform, BootConfig, RuntimeConfig, QemuConfig +from eosim.core.platform import Platform from eosim.core.registry import PlatformRegistry from eosim.engine.backend import SimResult diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index d589fc3..548d2d4 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -1 +1 @@ -# SPDX-License-Identifier: MIT \ No newline at end of file +# SPDX-License-Identifier: MIT diff --git a/tests/integration/test_cli_commands.py b/tests/integration/test_cli_commands.py index b8897e3..a0baa88 100644 --- a/tests/integration/test_cli_commands.py +++ b/tests/integration/test_cli_commands.py @@ -1,6 +1,7 @@ """Integration tests — CLI commands via Click test runner.""" import pytest from click.testing import CliRunner + from eosim.cli.main import cli diff --git a/tests/integration/test_platform_pipeline.py b/tests/integration/test_platform_pipeline.py index 4051c40..ff16ab7 100644 --- a/tests/integration/test_platform_pipeline.py +++ b/tests/integration/test_platform_pipeline.py @@ -1,14 +1,14 @@ """Integration tests — full platform pipeline: YAML → Platform → Engine → SimResult → Checks.""" -import os +from pathlib import Path + import pytest import yaml -from pathlib import Path from eosim.core.platform import Platform, discover_platforms from eosim.core.registry import PlatformRegistry from eosim.core.schema import validate_platform from eosim.engine.backend import EoSimEngine, SimResult, get_engine -from eosim.tests.runner import run_checks, load_checks +from eosim.tests.runner import load_checks, run_checks class TestPlatformPipeline: diff --git a/tests/integration/test_scenario_runner.py b/tests/integration/test_scenario_runner.py index 912a8bb..f234090 100644 --- a/tests/integration/test_scenario_runner.py +++ b/tests/integration/test_scenario_runner.py @@ -1,7 +1,8 @@ """Integration tests — YAML scenario loading and validation.""" +from pathlib import Path + import pytest import yaml -from pathlib import Path from eosim.engine.backend import SimResult from eosim.tests.runner import run_checks diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index d589fc3..548d2d4 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1 +1 @@ -# SPDX-License-Identifier: MIT \ No newline at end of file +# SPDX-License-Identifier: MIT diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index 81849db..af2baf7 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -1,10 +1,14 @@ # SPDX-License-Identifier: MIT """Unit tests for EoSim core.""" -import pytest, os, tempfile, yaml +import os + +import yaml + +from eosim.artifacts.manager import collect_artifacts, generate_junit from eosim.core.platform import Platform, discover_platforms -from eosim.engine.backend import SimResult, RenodeEngine, QemuEngine +from eosim.engine.backend import QemuEngine, RenodeEngine, SimResult from eosim.tests.runner import run_checks -from eosim.artifacts.manager import collect_artifacts, generate_junit + class TestPlatform: def test_from_yaml(self, tmp_path): @@ -139,7 +143,6 @@ def test_invalid_engine(self): class TestScenarios: def test_wait_for_pass(self): from eosim.tests.scenarios import run_scenario - from eosim.engine.backend import SimResult scenario = {"steps": [{"type": "wait_for", "pattern": "login:"}]} results = run_scenario(scenario, "Welcome login: root", 5.0) assert results[0].passed @@ -214,8 +217,9 @@ def test_submit_and_get(self, tmp_path): assert got.platform == "arm64-linux" def test_list_jobs(self, tmp_path): - from eosim.core.jobs import JobQueue import time + + from eosim.core.jobs import JobQueue q = JobQueue(str(tmp_path)) q.submit("arm64-linux") time.sleep(0.01) @@ -555,7 +559,7 @@ class TestDomains: """Tests for domain profiles and catalog.""" def test_catalog_complete(self): - from eosim.core.domains import DOMAIN_CATALOG, list_domains + from eosim.core.domains import list_domains domains = list_domains() assert len(domains) == 15 expected = ["automotive", "medical", "industrial", "consumer", @@ -592,7 +596,7 @@ class TestModeling: """Tests for modeling method catalog.""" def test_catalog_complete(self): - from eosim.core.modeling import MODELING_CATALOG, list_modeling_methods + from eosim.core.modeling import list_modeling_methods methods = list_modeling_methods() assert len(methods) == 10 expected = ["deterministic", "stochastic", "discrete-event", diff --git a/tests/unit/test_gui.py b/tests/unit/test_gui.py index c343951..2957680 100644 --- a/tests/unit/test_gui.py +++ b/tests/unit/test_gui.py @@ -52,20 +52,20 @@ def test_simulator_class_field(self): assert tpl.simulator_class, f"{key} simulator_class is empty" def test_valid_arch(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.core.schema import VALID_ARCHES + from eosim.gui.product_templates import PRODUCT_CATALOG for key, tpl in PRODUCT_CATALOG.items(): assert tpl.arch in VALID_ARCHES, f"{key} invalid arch: {tpl.arch}" def test_valid_domain(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.core.schema import VALID_DOMAINS + from eosim.gui.product_templates import PRODUCT_CATALOG for key, tpl in PRODUCT_CATALOG.items(): assert tpl.domain in VALID_DOMAINS, f"{key} invalid domain: {tpl.domain}" def test_valid_modeling(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.core.schema import VALID_MODELING + from eosim.gui.product_templates import PRODUCT_CATALOG for key, tpl in PRODUCT_CATALOG.items(): assert tpl.modeling in VALID_MODELING, f"{key} invalid modeling" @@ -170,7 +170,6 @@ class TestSensors: def test_temperature_sensor_tick(self): from eosim.engine.native.peripherals.sensors import TemperatureSensor s = TemperatureSensor('t', 0x1000) - initial = s.temperature for _ in range(100): s.simulate_tick() assert s._tick_count == 100 @@ -536,35 +535,35 @@ def test_factory_creates_vehicle(self): def test_factory_creates_drone(self): from eosim.engine.native import VirtualMachine - from eosim.engine.native.simulators import SimulatorFactory, DroneSimulator + from eosim.engine.native.simulators import DroneSimulator, SimulatorFactory vm = VirtualMachine(name="t", arch="arm", ram_mb=32) sim = SimulatorFactory.create("drone_controller", vm) assert isinstance(sim, DroneSimulator) def test_factory_creates_medical(self): from eosim.engine.native import VirtualMachine - from eosim.engine.native.simulators import SimulatorFactory, MedicalSimulator + from eosim.engine.native.simulators import MedicalSimulator, SimulatorFactory vm = VirtualMachine(name="t", arch="arm", ram_mb=32) sim = SimulatorFactory.create("medical_monitor", vm) assert isinstance(sim, MedicalSimulator) def test_factory_creates_robot(self): from eosim.engine.native import VirtualMachine - from eosim.engine.native.simulators import SimulatorFactory, RobotSimulator + from eosim.engine.native.simulators import RobotSimulator, SimulatorFactory vm = VirtualMachine(name="t", arch="arm", ram_mb=32) sim = SimulatorFactory.create("robot_controller", vm) assert isinstance(sim, RobotSimulator) def test_factory_fallback_generic(self): from eosim.engine.native import VirtualMachine - from eosim.engine.native.simulators import SimulatorFactory, BaseSimulator + from eosim.engine.native.simulators import BaseSimulator, SimulatorFactory vm = VirtualMachine(name="t", arch="arm", ram_mb=32) sim = SimulatorFactory.create("unknown_product", vm) assert isinstance(sim, BaseSimulator) def test_factory_all_product_types_mapped(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.engine.native.simulators import SIMULATOR_MAP + from eosim.gui.product_templates import PRODUCT_CATALOG for key in PRODUCT_CATALOG: assert key in SIMULATOR_MAP, f"Product '{key}' not in SIMULATOR_MAP" @@ -705,6 +704,7 @@ def _skip_no_tk(self): def test_update_state_from_dict(self): """CPUPanel.update_state should accept a dict and store values.""" import tkinter as tk + from eosim.gui.widgets.cpu_panel import CPUPanel root = tk.Tk() root.withdraw() @@ -733,6 +733,7 @@ def test_update_state_from_dict(self): def test_update_state_from_cpu_state_object(self): """CPUPanel.update_state should accept a CPUState-like object.""" import tkinter as tk + from eosim.gui.widgets.cpu_panel import CPUPanel root = tk.Tk() root.withdraw() @@ -758,6 +759,7 @@ class FakeCPU: def test_reset_clears_state(self): """CPUPanel.reset should zero out all previous values.""" import tkinter as tk + from eosim.gui.widgets.cpu_panel import CPUPanel root = tk.Tk() root.withdraw() @@ -942,9 +944,15 @@ def test_medical_sensor_disconnect(self): def test_all_simulators_have_scenarios(self): """Every non-base simulator should have a SCENARIOS dict.""" from eosim.engine.native.simulators import ( - VehicleSimulator, DroneSimulator, MedicalSimulator, - RobotSimulator, AircraftSimulator, IndustrialSimulator, - IoTSimulator, SatelliteSimulator, EnergySimulator, + AircraftSimulator, + DroneSimulator, + EnergySimulator, + IndustrialSimulator, + IoTSimulator, + MedicalSimulator, + RobotSimulator, + SatelliteSimulator, + VehicleSimulator, WearableSimulator, ) for cls in [VehicleSimulator, DroneSimulator, MedicalSimulator, @@ -957,7 +965,7 @@ def test_all_simulators_have_scenarios(self): def test_all_simulators_load_scenario(self): """Every simulator's load_scenario should set scenario name in state.""" from eosim.engine.native import VirtualMachine - from eosim.engine.native.simulators import SimulatorFactory, SIMULATOR_MAP + from eosim.engine.native.simulators import SIMULATOR_MAP, SimulatorFactory tested = set() for product_type, cls in SIMULATOR_MAP.items(): if cls.__name__ in tested or cls.__name__ == 'BaseSimulator': diff --git a/tests/unit/test_integrations.py b/tests/unit/test_integrations.py index 56bc966..8bc3896 100644 --- a/tests/unit/test_integrations.py +++ b/tests/unit/test_integrations.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT """Tests for external tool integrations — XPlane, Gazebo, OpenFOAM.""" -import pytest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch class TestXPlaneConnection: @@ -32,10 +31,10 @@ def test_connect_success(self, mock_socket_cls): @patch('socket.socket') def test_connect_failure(self, mock_socket_cls): - import socket + from eosim.integrations.xplane import XPlaneConnection mock_sock = MagicMock() - mock_sock.connect.side_effect = socket.error("Connection refused") + mock_sock.connect.side_effect = OSError("Connection refused") mock_socket_cls.return_value = mock_sock conn = XPlaneConnection() result = conn.connect() @@ -181,24 +180,27 @@ class TestEngineDispatch: """Test get_engine dispatches correctly for new engine types.""" def test_xplane_engine_dispatch(self): - from eosim.engine.backend import get_engine, XPlaneEngine from unittest.mock import MagicMock + + from eosim.engine.backend import XPlaneEngine, get_engine platform = MagicMock() platform.engine = 'xplane' engine = get_engine(platform) assert isinstance(engine, XPlaneEngine) def test_gazebo_engine_dispatch(self): - from eosim.engine.backend import get_engine, GazeboEngine from unittest.mock import MagicMock + + from eosim.engine.backend import GazeboEngine, get_engine platform = MagicMock() platform.engine = 'gazebo' engine = get_engine(platform) assert isinstance(engine, GazeboEngine) def test_openfoam_engine_dispatch(self): - from eosim.engine.backend import get_engine, OpenFOAMEngine from unittest.mock import MagicMock + + from eosim.engine.backend import OpenFOAMEngine, get_engine platform = MagicMock() platform.engine = 'openfoam' engine = get_engine(platform) diff --git a/tests/unit/test_new_domains.py b/tests/unit/test_new_domains.py index 59ac032..c4a43c9 100644 --- a/tests/unit/test_new_domains.py +++ b/tests/unit/test_new_domains.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT """Extended unit tests for EoSim core — new domains, modeling, schema, simulators.""" -import pytest class TestDomainsExtended: @@ -14,7 +13,7 @@ def test_new_domain_names(self): from eosim.core.domains import DOMAIN_CATALOG new_domains = ["aerodynamics", "physiology", "finance", "weather", "gaming"] for d in new_domains: - assert d in DOMAIN_CATALOG, "Missing domain: %s" % d + assert d in DOMAIN_CATALOG, f"Missing domain: {d}" def test_aerodynamics_fields(self): from eosim.core.domains import get_domain @@ -57,8 +56,8 @@ def test_all_domains_have_required_fields(self): from eosim.core.domains import DOMAIN_CATALOG for name, d in DOMAIN_CATALOG.items(): assert d.name == name - assert d.display_name, "%s missing display_name" % name - assert d.description, "%s missing description" % name + assert d.display_name, f"{name} missing display_name" + assert d.description, f"{name} missing description" assert isinstance(d.standards, list) assert isinstance(d.typical_arches, list) assert isinstance(d.test_scenarios, list) @@ -82,7 +81,7 @@ def test_new_method_names(self): from eosim.core.modeling import MODELING_CATALOG new_methods = ["cfd", "monte-carlo", "finite-element", "particle-based"] for m in new_methods: - assert m in MODELING_CATALOG, "Missing method: %s" % m + assert m in MODELING_CATALOG, f"Missing method: {m}" def test_cfd_engine_support(self): from eosim.core.modeling import get_modeling @@ -113,8 +112,8 @@ def test_particle_based_fields(self): def test_all_methods_have_engine_support(self): from eosim.core.modeling import MODELING_CATALOG for name, m in MODELING_CATALOG.items(): - assert len(m.engine_support) > 0, "%s has no engine_support" % name - assert "eosim" in m.engine_support, "%s missing eosim engine" % name + assert len(m.engine_support) > 0, f"{name} has no engine_support" + assert "eosim" in m.engine_support, f"{name} missing eosim engine" class TestSchemaExtended: @@ -125,21 +124,21 @@ def test_new_domains_valid(self): for domain in ["aerodynamics", "physiology", "finance", "weather", "gaming"]: data = {"name": "t", "arch": "arm64", "engine": "eosim", "domain": domain} errors = validate_platform(data) - assert errors == [], "Domain %s should be valid, got: %s" % (domain, errors) + assert errors == [], f"Domain {domain} should be valid, got: {errors}" def test_new_modeling_valid(self): from eosim.core.schema import validate_platform for method in ["cfd", "monte-carlo", "finite-element", "particle-based"]: data = {"name": "t", "arch": "x86_64", "engine": "eosim", "modeling": method} errors = validate_platform(data) - assert errors == [], "Modeling %s should be valid, got: %s" % (method, errors) + assert errors == [], f"Modeling {method} should be valid, got: {errors}" def test_new_engines_valid(self): from eosim.core.schema import validate_platform for eng in ["xplane", "gazebo", "openfoam"]: data = {"name": "t", "arch": "x86_64", "engine": eng} errors = validate_platform(data) - assert errors == [], "Engine %s should be valid, got: %s" % (eng, errors) + assert errors == [], f"Engine {eng} should be valid, got: {errors}" def test_invalid_domain_still_fails(self): from eosim.core.schema import validate_platform @@ -205,7 +204,7 @@ def test_aerodynamics_state_keys(self): sim.tick() state = sim.get_state() for key in ['airspeed_mps', 'mach_number', 'cl', 'cd', 'lift_n', 'drag_n']: - assert key in state, "Missing key: %s" % key + assert key in state, f"Missing key: {key}" def test_physiology_state_keys(self): from eosim.engine.native import VirtualMachine @@ -215,7 +214,7 @@ def test_physiology_state_keys(self): sim.tick() state = sim.get_state() for key in ['heart_rate', 'spo2', 'bp_sys', 'bp_dia', 'temperature']: - assert key in state, "Missing key: %s" % key + assert key in state, f"Missing key: {key}" def test_finance_state_keys(self): from eosim.engine.native import VirtualMachine @@ -225,7 +224,7 @@ def test_finance_state_keys(self): sim.tick() state = sim.get_state() for key in ['price', 'bid', 'ask', 'volume', 'pnl']: - assert key in state, "Missing key: %s" % key + assert key in state, f"Missing key: {key}" def test_weather_state_keys(self): from eosim.engine.native import VirtualMachine @@ -235,7 +234,7 @@ def test_weather_state_keys(self): sim.tick() state = sim.get_state() for key in ['temperature_c', 'pressure_hpa', 'wind_speed_mps', 'humidity_pct']: - assert key in state, "Missing key: %s" % key + assert key in state, f"Missing key: {key}" def test_gaming_state_keys(self): from eosim.engine.native import VirtualMachine @@ -245,4 +244,4 @@ def test_gaming_state_keys(self): sim.tick() state = sim.get_state() for key in ['player_pos', 'entity_count', 'collision_count', 'fps']: - assert key in state, "Missing key: %s" % key + assert key in state, f"Missing key: {key}" diff --git a/tests/unit/test_new_gui.py b/tests/unit/test_new_gui.py index e5d66c6..ad675f4 100644 --- a/tests/unit/test_new_gui.py +++ b/tests/unit/test_new_gui.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT """Extended GUI tests — new product templates, simulators, renderers, panels.""" -import pytest class TestNewProductTemplates: @@ -15,7 +14,7 @@ def test_new_template_names(self): new_templates = ["wind_tunnel", "patient_model", "stock_market", "weather_station_sim", "game_world"] for t in new_templates: - assert t in PRODUCT_CATALOG, "Missing template: %s" % t + assert t in PRODUCT_CATALOG, f"Missing template: {t}" def test_wind_tunnel_fields(self): from eosim.gui.product_templates import get_template @@ -75,24 +74,24 @@ def test_new_templates_have_required_fields(self): elif field == "peripherals": assert isinstance(val, list) and len(val) > 0 else: - assert val, "%s.%s is empty" % (key, field) + assert val, f"{key}.{field} is empty" def test_new_templates_valid_domain(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.core.schema import VALID_DOMAINS + from eosim.gui.product_templates import PRODUCT_CATALOG new = ["wind_tunnel", "patient_model", "stock_market", "weather_station_sim", "game_world"] for key in new: tpl = PRODUCT_CATALOG[key] - assert tpl.domain in VALID_DOMAINS, "%s invalid domain: %s" % (key, tpl.domain) + assert tpl.domain in VALID_DOMAINS, f"{key} invalid domain: {tpl.domain}" def test_new_templates_valid_modeling(self): - from eosim.gui.product_templates import PRODUCT_CATALOG from eosim.core.schema import VALID_MODELING + from eosim.gui.product_templates import PRODUCT_CATALOG new = ["wind_tunnel", "stock_market", "game_world"] for key in new: tpl = PRODUCT_CATALOG[key] - assert tpl.modeling in VALID_MODELING, "%s invalid modeling: %s" % (key, tpl.modeling) + assert tpl.modeling in VALID_MODELING, f"{key} invalid modeling: {tpl.modeling}" class TestNewSimulatorMap: @@ -141,7 +140,7 @@ def test_new_renderers_registered(self): from eosim.gui.renderers import get_renderer for domain in ['aerodynamics', 'physiology', 'finance', 'weather', 'gaming']: r = get_renderer(domain) - assert r.DOMAIN == domain, "Renderer for %s not found" % domain + assert r.DOMAIN == domain, f"Renderer for {domain} not found" def test_renderer_has_setup_and_update(self): from eosim.gui.renderers import get_renderer @@ -158,8 +157,11 @@ class TestNewPeripheralPanels: def test_panel_classes_exist(self): from eosim.gui.widgets.peripheral_panel import ( - AerodynamicsPanel, PhysiologyPanel, FinancePanel, - WeatherPanel, GamingPanel, + AerodynamicsPanel, + FinancePanel, + GamingPanel, + PhysiologyPanel, + WeatherPanel, ) for cls in [AerodynamicsPanel, PhysiologyPanel, FinancePanel, WeatherPanel, GamingPanel]: @@ -170,7 +172,7 @@ def test_panel_classes_exist(self): def test_domain_panel_map_has_new_entries(self): from eosim.gui.widgets.peripheral_panel import DOMAIN_PANEL_MAP for domain in ['aerodynamics', 'physiology', 'finance', 'weather', 'gaming']: - assert domain in DOMAIN_PANEL_MAP, "Missing domain panel: %s" % domain + assert domain in DOMAIN_PANEL_MAP, f"Missing domain panel: {domain}" def test_device_domain_map_has_new_entries(self): from eosim.gui.widgets.peripheral_panel import DEVICE_DOMAIN_MAP @@ -180,7 +182,7 @@ def test_device_domain_map_has_new_entries(self): 'PhysicsEngine', 'EntityManager', 'GameController', ] for dev in new_devices: - assert dev in DEVICE_DOMAIN_MAP, "Missing device mapping: %s" % dev + assert dev in DEVICE_DOMAIN_MAP, f"Missing device mapping: {dev}" class TestNewBuildPanelPeripherals: @@ -191,4 +193,4 @@ def test_new_peripherals_in_all_peripherals(self): new_periphs = ['heart_model', 'bp_sensor', 'market_feed', 'order_book', 'anemometer', 'radar', 'physics_engine', 'terrain', 'entities'] for p in new_periphs: - assert p in ALL_PERIPHERALS, "Missing peripheral: %s" % p + assert p in ALL_PERIPHERALS, f"Missing peripheral: {p}"