7466a46aa7
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>
162 lines
6.4 KiB
PowerShell
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
|