Files
wwtools/mbproxy/tests/sim/run-dl205-sim.ps1
T
Joseph Doherty 7466a46aa7 mbproxy/docs: retire superseded design/plan docs and dissolve DL260/
The standalone design.md, kpi.md, operations.md, and the docs/plan/
phase tree were point-in-time planning artefacts now superseded by the
topic-organized docs/ tree (Architecture/, Features/, Operations/,
Reference/, Testing/). The DL260/ folder mixed a device-reference doc, a
test fixture, a sample test, and a screenshot; its contents now live in
their natural homes (dl205.md + mbtcp_settings.JPG under docs/Reference/,
dl205.json next to its launcher in tests/sim/, sample test dropped).

All cross-references in the surviving docs, README, CLAUDE.md, the config
template, and source comments are repointed to the new locations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 07:37:48 -04:00

162 lines
6.4 KiB
PowerShell

#Requires -Version 7
<#
.SYNOPSIS
Provision a Python venv and launch the pymodbus DL205 simulator.
.DESCRIPTION
Idempotent: re-runs skip venv provisioning when tests/sim/.venv is fully provisioned.
Spawns 'pymodbus.simulator' with the DL205/DL260 register profile on a configurable
port; the server process stays attached so Ctrl-C (or parent exit) kills it cleanly.
pymodbus version pin: 3.13.0
(Matches the profile comment in dl205.json. Record the version here AND in
tests/sim/README.md so it is never lost across re-provisioning.)
API note: pymodbus 3.13.0 uses 'pymodbus.simulator' (not the legacy 'pymodbus.server
run' command). The Modbus TCP port is set in the JSON config; this script writes a
temp config that overrides the port so the free-port-picker pattern works.
aiohttp is required by the pymodbus simulator HTTP console and is installed alongside
pymodbus.
.PARAMETER Profile
Path to the pymodbus JSON profile. Defaults to dl205.json in this script's
directory (i.e. the checked-in DL205 quirk profile).
.PARAMETER Port
TCP port for the Modbus server to listen on. Defaults to 5020.
.EXIT CODES
0 Clean exit (Ctrl-C or natural termination).
1 Python not found, or venv provisioning failed.
2 pymodbus.simulator launch failed.
3 Profile file not found.
#>
[CmdletBinding()]
param(
[string]$Profile = (Join-Path $PSScriptRoot 'dl205.json'),
[int]$Port = 5020
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# ── 1. Resolve and validate the profile path ─────────────────────────────────
$ProfileResolved = (Resolve-Path -Path $Profile -ErrorAction SilentlyContinue)?.Path
if (-not $ProfileResolved) {
Write-Error "Profile not found: $Profile"
exit 3
}
# ── 2. Locate Python ─────────────────────────────────────────────────────────
# Try 'python' first (standard PATH install), then the Windows-store launcher 'py'.
$pythonExe = $null
foreach ($candidate in 'python', 'py') {
try {
$ver = & $candidate --version 2>&1
if ($LASTEXITCODE -eq 0) {
$pythonExe = $candidate
Write-Host "[sim] Python found via '$candidate': $ver"
break
}
} catch {
# not on PATH — continue
}
}
if (-not $pythonExe) {
Write-Error @"
Python 3.10+ is required to run the DL205 simulator but was not found on PATH.
Install Python from https://www.python.org/downloads/ and ensure it is on your PATH,
or use the Windows Store launcher ('py').
"@
exit 1
}
# ── 3. Provision the venv (idempotent) ───────────────────────────────────────
# pymodbus version pin: 3.13.0
# Update this constant AND tests/sim/README.md together if you re-pin.
$PYMODBUS_VERSION = '3.13.0'
$venvDir = Join-Path $PSScriptRoot '.venv'
$venvPython = Join-Path $venvDir 'Scripts\python.exe'
$pipExe = Join-Path $venvDir 'Scripts\pip.exe'
$simulatorExe = Join-Path $venvDir 'Scripts\pymodbus.simulator.exe' # sentinel for complete install
# Provisioning is idempotent: we only skip it when pymodbus.simulator.exe exists.
# Checking only the .venv directory is not enough — a previous run killed mid-install
# leaves the directory but without pymodbus installed.
$needsProvision = (-not (Test-Path $simulatorExe))
if ($needsProvision) {
if (-not (Test-Path $venvDir)) {
Write-Host "[sim] Creating venv at $venvDir ..."
& $pythonExe -m venv $venvDir
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to create Python venv (exit $LASTEXITCODE)."
exit 1
}
} else {
Write-Host "[sim] Venv exists but pymodbus is not fully installed — installing now."
}
# pymodbus 3.13.0 does not provide a [server] extra; the simulator module is
# included in the base package. aiohttp is required by the simulator's HTTP
# console and is not a declared dependency of pymodbus, so we install it
# explicitly here.
Write-Host "[sim] Installing pymodbus==$PYMODBUS_VERSION + aiohttp ..."
& $pipExe install "pymodbus==$PYMODBUS_VERSION" aiohttp
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to install pymodbus / aiohttp (exit $LASTEXITCODE). Check network or proxy settings."
exit 1
}
Write-Host "[sim] Venv provisioned."
} else {
Write-Host "[sim] Venv and pymodbus already provisioned — skipping."
}
# ── 4. Prepare a port-specific config file ───────────────────────────────────
# pymodbus.simulator 3.13.0 reads the Modbus TCP port from the JSON config, not
# from a command-line --port flag. To allow the fixture's free-port-picker pattern,
# we write a temp config that is a copy of the base profile but with srv.port
# overridden to $Port.
$tempConfig = [System.IO.Path]::GetTempFileName() + '.json'
try {
$json = Get-Content -Raw $ProfileResolved | ConvertFrom-Json -Depth 20
$json.server_list.srv.port = $Port
$json | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tempConfig
Write-Host "[sim] Wrote temp config with port=$Port to: $tempConfig"
}
catch {
Write-Error "Failed to prepare port-specific config: $_"
exit 2
}
# ── 5. Launch pymodbus simulator ─────────────────────────────────────────────
# pymodbus 3.13.0 API: pymodbus.simulator --json_file <path> --modbus_server <key>
# --modbus_device <key>
# We don't pass --http_port because we don't need the REST API in tests.
# The process is kept alive in the foreground; Ctrl-C (or parent-exit Kill) stops it.
Write-Host "[sim] Starting pymodbus DL205 simulator on Modbus TCP port $Port ..."
try {
& $simulatorExe `
--json_file $tempConfig `
--modbus_server srv `
--modbus_device dev
$exitCode = $LASTEXITCODE
} catch {
Write-Error "Failed to launch pymodbus.simulator: $_"
Remove-Item -Force $tempConfig -ErrorAction SilentlyContinue
exit 2
} finally {
Remove-Item -Force $tempConfig -ErrorAction SilentlyContinue
}
# A non-zero exit from pymodbus is unexpected (0 = clean shutdown).
if ($exitCode -ne 0) {
Write-Error "pymodbus.simulator exited with code $exitCode."
exit 2
}
exit 0