Production Lines
Serial material flow, station states, production signals, and OEE inputs for assets in a template line.
Define a line
A flow is serial and one-to-one, with an unlimited source and a sink. There is no routing, joining, bill of materials, or component consumption. Each station references a unique explicit assignment id in the same line, and that assignment must have count: 1. Assets that are not stations stay independent utilities.
areas:
- name: production
lines:
- name: machining
assets:
- { model: cnc-spindle, id: cnc-001 }
- { model: belt-conveyor, id: wash-001 }
flow:
source: unlimited
stations:
- asset: cnc-001
ideal_cycle_s: 45
scrap_rate: 0.01
buffer_after: { capacity: 10, initial: 2 }
- { asset: wash-001, ideal_cycle_s: 30 }Every station except the last needs a buffer_after with a positive capacity. The initial level must be between zero and the capacity. Scrap leaves the line immediately. Good parts enter the next buffer, and the final station feeds the sink. Interrupted work keeps its progress.
Scheduling and precedence
In each simulated substep, the line resolves events after the assets are evaluated, then commits one consistent snapshot. Completions that happen at the same instant resolve upstream first, so asset declaration order does not affect flow.
A station is in exactly one state. When several apply, the first in this list wins:
off_shift, down_planned, down_unplanned, blocked, starved, running.
idle is reserved for scheduled stations with nothing to do. A serial line with an unlimited source normally always has work. A full outgoing buffer blocks a station. An empty incoming buffer starves it, unless it already holds an interrupted part.
Shifts
The optional top-level shifts block sets a weekly local-time pattern. Simulated time zero equals shifts.start (default mon 06:00). timezone_offset_h describes the local calendar and does not shift a start time that is already local. Overnight shifts work. Without shifts, stations are always scheduled.
shifts:
start: mon 06:00
timezone_offset_h: 0
pattern:
- days: [mon, tue, wed, thu, fri]
start: "06:00"
end: "14:00"
breaks: [{ start: "10:00", minutes: 15 }]
changeovers:
- { station: cnc-001, every_parts: 500, minutes: 20 }Changeovers count every completed part, good and scrap. Their durations run in simulated time, including through breaks and off-shift time. Breaks and changeovers are down_planned.
Stopping a station
A user-written run_command: false puts the station in down_planned with reason manual_stop. Only explicit line membership enables this gating. When the station stops or runs, Miravo also writes the writable Boolean run_command itself (false when stopped, true when running) without creating a user override. Physics settles through the model's own generators over the following substeps.
Fault effects
A fault can declare production_effect: stop, { derate: 0.5 }, or { scrap: 0.3 }. Active derates multiply, and the effective cycle time is the ideal time divided by that product.
The scrap probability is the station scrap_rate plus the lifecycle stage scrap_rate_add plus any active fault scrap, clamped to [0, 1]. A scrap probability of zero consumes no random draws. Each station's random stream derives from the seed and the asset identity, so it does not depend on spawn order. Production uses that same stream.
Published members
Miravo appends these read-only members to each station after the model's own members. Non-station assets do not get them.
| Members | Type and meaning |
|---|---|
state | UInt16: running=0, idle=1, starved=2, blocked=3, down_planned=4, down_unplanned=5, off_shift=6 |
state_label, stop_reason | Strings. stop_reason is a fault name, break, changeover, manual_stop, blocked, starved, off_shift, or empty. |
good_count, scrap_count | UInt32 counters that wrap at 2³² |
ideal_cycle_time, actual_cycle_time | Double seconds. Actual is the elapsed time of the last completed cycle, including interruptions. |
time_<state>_s | Seven Double cumulative durations, one per state |
scheduled_time_s | Double seconds of planned production time: everything except off_shift and shift break. Changeover, manual stop, faults, blocked and starved time stay in. time_down_planned_s still counts breaks. |
run_id | String UUID per line. It changes on start, reset, and line recreation or reload. |
A restart is a fresh run. Counters do not persist. The CNC, press, and conveyor models carry no production counters or availability_state of their own, so use the injected members.
MQTT and OPC UA publish every injected member. Modbus appends the numeric ones inside the asset block and skips Strings. miravo status --json includes state.lines summaries with stations and buffer levels, and /api/state includes each station's production signals.
Loading and removing lines
A station loaded at runtime publishes a complete initial snapshot, with zero counters and timers, in the next tick delta. Writing Start to a stopped station is corrected in the actual run_command and in the published delta.
To remove a line, unload its template with miravo unload <template> or from the Console. miravo remove rejects individual stations. A runtime load checks for explicit-ID collisions and injected-member conflicts first, and a failed load rolls back without reserving station IDs. Reloading one line leaves the run_id of other lines unchanged. Litmus Edge exports use the same generated names as the runtime, so explicit IDs do not advance the prefix counters.
OEE reference
examples/oee-reference/ reads published signals only:
- availability = running / scheduled (planned production time: shift breaks excluded, changeovers and manual stops included)
- performance = total × ideal / running
- quality = good / total
- OEE = the product of the three
To measure one shift, start with miravo start --seed 42 --speed 1000 --run-for 8h. The engine pauses at exactly 28800 simulated seconds while the signals stay readable. Compute the ratios, then miravo stop.
Zero denominators give zero, and ratios are not clipped. Do not mix different run_id values, and do not use wrapped lifetime counters across a wrap. The deterministic fault example stops station two for 30 seconds. It shows blocking, starvation, and an exact three-part deficit at the sink over two hours.
Was this page useful?
Last updated on
Miravo