CLI quickstart
This walks through a complete tune from the command line against BHTune's built-in FOPDT process simulator — no OPC DA connection, gateway, or real plant equipment required. It's the fastest way to see the whole tuning lifecycle (relay test → calculated PID constants → history) end to end.
Run a zero-configuration demo tune
bhtune simulate
simulate is a defaulted subset of the full tune command (see CLI reference
for every flag tune accepts against a real loop) — it needs no flags at all to run a complete
MRFT test against a synthetic Flow-type loop, using a fixed internal PV/MV tag pair instead of a
real OPC DA tag. A run typically takes under a minute:
No PID constant tags configured for this run's driver/template; skipping write-back.
Tune completed successfully (run id 1).
("No PID constant tags configured" is expected and correct here — the simulator driver has no
PID constant tags to write to at all, so write-back is always skipped for simulate regardless
of --write-pid. See bhtune tune for writing PID constants
back to a real controller.)
Every run is persisted to BHTune's SQLite database (see Installation) the moment it starts, not just on completion, so even an aborted or crashed run leaves a record.
Look at what it calculated
bhtune history show 1
Run #1 — Tag name: Sim.Loop1.PV
Notes: —
Driver: Simulator
Outcome: Completed
Started at: 2026-08-16T06:51:28.165183623+00:00
Completed at: 2026-08-16T06:52:18.574446068+00:00
Template: Yokogawa CentumVP (Builtin)
Process/controller: Flow / Pi
Relay amplitude: 10%
Cycles skip/count: 1 / 2
Initial PV / MV: 50 / 50
MV range: 0 - 100
PV range: 0 - 100
Direction: Reverse
Samples recorded: 64
Timing:
Basis: FixedStep
Requested interval: 800 ms
Observed sample gaps: 63
Mean / max sample gap: 800.000 / 800.000 ms
Missed poll opportunities: 0
Oscillation period: 46500.000 ms
Approx. samples / period: 58.125
Sampling adequacy: adequate
Poll latency:
PV reads: count=64 mean=0.010 ms max=0.050 ms
MV writes: none
MV verification: none
Sample persistence: count=64 mean=0.100 ms max=0.500 ms
Total tick work: count=64 mean=0.200 ms max=1.000 ms
Restore: confirmed
Calculated results:
LEVEL STATUS KP TI(min) TD(min) PROP INTEGRAL DERIV REASON
Aggressive Valid 0.5885 0.0971 0.0000 169.9304 5.8256 0.0000 -
Moderate Valid 0.3941 0.0971 0.0000 253.7703 5.8256 0.0000 -
Sluggish Valid 0.2949 0.0971 0.0000 339.1090 5.8256 0.0000 -
Three response levels are always evaluated (Aggressive/Moderate/Sluggish) — see
MRFT concepts for what they mean and how to pick one. A usable
row has STATUS set to Valid; if the measured amplitude, period, or a converted PID value is
zero, non-finite, or otherwise unusable, the row is retained as Invalid with a diagnostic
reason and no numeric values, and cannot be written back. PROP and INTEGRAL here are the
Yokogawa CentumVP template's own units (Proportional Band % and Reset Time in minutes); a
different template reports these in whatever units that DCS/PLC family expects — see
DCS/PLC templates.
history show also prints timing diagnostics, including the requested and observed cadence,
sampling adequacy (adequate, marginal, or not assessed), and successful latency summaries
for PV reads, MV writes, MV verification, sample persistence, and total tick work. Sampling
adequacy is advisory: marginal does not automatically reject a valid result, but it is a
reason to inspect the trend before applying constants.
Every run's exact numbers depend on the simulator's process parameters
(--sim-gain/--sim-tau/--sim-dead-time/--sim-noise/--sim-seed). The simulator
advances its process and MRFT timestamps by the same configured poll step, so zero-noise runs
with the same inputs are reproducible even when host scheduling differs. A fixed --sim-seed
also reproduces the configured noise sequence within the same supported build.
List every run so far, and export one run's per-tick samples:
bhtune history list
bhtune export 1 --format csv > run-1-samples.csv
By default nothing is ever deleted automatically — BHTune retains every run forever until you
opt in to a retention policy. Set --retention-days/BHTUNE_RETENTION_DAYS/retention_days to
a positive whole number in bhtune.toml (see Configuration reference)
to delete runs older than that many days automatically on every startup, or run bhtune history prune --older-than-days <N> to prune on demand without waiting for the next startup — add
--dry-run to see how many runs would be deleted first.
Try different loop and controller types
simulate accepts the same --process-type/--controller-type/--relay-amp flags as tune:
bhtune simulate --process-type temperature-heat-exchange --controller-type pid --relay-amp 15
PID (as opposed to P/PI) is only offered for the two Temperature process types, matching the tag conventions the built-in templates were authored against.
Point it at a real loop
Once opcda-bridge-gateway is reachable from wherever BHTune runs, bhtune tune runs the same
test against a real OPC DA tag instead of the simulator:
bhtune tune \
--driver opcda --server Matrikon.OPC.Simulation.1 --bridge-host gateway.plant.local:7600 \
--tagname FIC101 --template "Yokogawa CentumVP" \
--process-type flow --controller-type pi --relay-amp 5
This reads live tags (PV, MV, ranges, mode, direction), switches the loop to manual, strokes the
relay, and restores the loop when the test ends — see Safety before
running this against anything connected to a real process, especially unattended. Live MRFT
timestamps use monotonic elapsed time anchored to UTC, so NTP/manual clock changes cannot distort
the measured relay period; real host, gateway, and OPC latency remains visible. Keep the host and
gateway responsive and use a poll interval comfortably shorter than the expected oscillation
period. Afterward, bhtune history show <run-id> reports the requested and observed sampling
cadence, measured oscillation period, approximate samples per period, sampling adequacy, and
successful operation-latency summaries. A live sample gap at least twice the requested interval
is reported as a missed-poll warning without aborting the run or blocking write-back. To also
write the calculated PID constants back:
bhtune tune ... --write-pid moderate --yes
--yes is mandatory alongside --write-pid for any non-interactive write — see
Safety.
Scripting and automation
--output json emits exactly one parseable JSON value on stdout on every path (success,
timeout, abort, write-back failure), with meaningful, distinguished process exit codes, so a
scheduler can tell a clean completion, a Ctrl+C abort, and a failed write-back apart without
parsing prose — see Automation and the exit code table in
AGENTS.md
for the full contract.
Next steps
- Web GUI quickstart — the same tuning engine, driven from a browser.
- Safety — what happens on Ctrl+C, a stalled read, or a timeout, and how PID write-back is verified and rolled back.
- DCS/PLC templates — the tag-mapping system, and how to contribute a template for a control system BHTune doesn't cover yet.