Tutorial: your first system model
In this tutorial you model a home heating system from a blank page: a thermostat on the wall measures the room temperature and a boiler control board fires the burner when the room is too cold. You go through every level of the Arcadia method, link the levels with traces, add tests, and finish with a model that passes the production gate.
It takes about 45 minutes. No installation is needed: everything runs in your browser. If MBSE is new to you, read MBSE in 15 minutes first; it explains the terms used here.
Each step shows the ArcLang text that the step adds. Every sample on this page is compiled when the site is built, so it is exactly what the compiler accepts. The complete model is at the end of the page.
Open a blank model
- Open the playground on a computer. It opens VS Code for the Web, with the ArcLang extension. (On a phone, the playground opens a lighter editor without the workbench.)
- Open the command palette (⌘⇧P on macOS, Ctrl+Shift+P elsewhere) and run the command ArcLang: New model… from it.
- Choose Blank model and name the file
home_heating.arc.
The editor shows the text on the left and the workbench on the right. The workbench draws the model; the text is the model. Both stay in step: a box you draw is written into the text, and a line you type is drawn.
The new file holds only a header:
model HomeHeating {
name: "Home heating"
version: "0.1"
}The workbench says the model is empty and offers Add a level…. Each level of the method opens an empty sheet with a palette: the tools that create the elements of that level. You use it at every step below.
A word on identifiers. Every element has an id, a stable name that other elements use to point at it. The workbench gives each element you draw an id, and you can change it in the Properties form. This page uses short readable ids such as SF-MEASURE (SF for system function). Use the same ones if you want your text to match the samples.
Step 1: write the requirements
Start with what the system must achieve. Click Add a level… and choose Requirements. An empty requirements block appears in the text. Type the three requirements into it:
requirements {
req "REQ-001" "Comfort" {
description: "The system shall keep the living room within 0.5 °C of the set temperature"
priority: "High"
}
req "REQ-002" "Reaction time" {
description: "The system shall start or stop the burner within 2 s of a reading that crosses the set temperature"
priority: "Medium"
}
req "REQ-003" "Set temperature" {
description: "The occupant shall be able to change the set temperature on the thermostat"
priority: "Medium"
}
}Each req has an identifier, a title, a description written as a checkable statement ("shall"), and a priority. Identifiers that contain a hyphen are written between quotes.
Step 2: operational analysis
The operational analysis describes the need, before any solution: who is involved (actors and entities), what they do (activities), and what passes between them (interactions). Here, the occupant tells the home what temperature they want, and the home keeps the rooms warm.
Click Add a level… and choose Operational. The sheet that opens has a palette with Actor, Entity, Activity, Capability and Interaction.
- Pick Actor and name it
Occupant. - Pick Entity and name it
Home. - Select the
Homebox, pick Activity and name itTake the wished temperature. Do it again forKeep the rooms warm. An activity created on a selected entity is performed by it, so it is written inside the entity. - Pick Interaction, click
Occupant, then the first activity, and name itWished temperature. Draw a second interaction from the first activity to the second. - Pick Capability and name it
Live in a warm home. An operational capability is what the actors achieve together.
The text now holds the whole level. Fill the descriptions and the capability's involves list in the Properties form or directly in the text:
operational_analysis "Operational Analysis" {
operational_capability "Live in a warm home" {
id: "OC-WARM"
description: "The home stays at the temperature its occupants want"
involves: ["OA-OCCUPANT", "OA-HOME"]
}
actor "Occupant" {
id: "OA-OCCUPANT"
description: "A person who lives in the home"
}
entity "Home" {
id: "OA-HOME"
description: "The house, its rooms and its boiler"
activity "Take the wished temperature" { id: "OA-WISH" }
activity "Keep the rooms warm" { id: "OA-WARM" }
}
interaction "Wished temperature" { from: "OA-OCCUPANT" to: "OA-WISH" }
interaction "Temperature to reach" { from: "OA-WISH" to: "OA-WARM" }
}Nothing here mentions a thermostat or a boiler. That is the point of this level.
Step 3: system analysis
The system analysis looks at the system as a black box: what it must do for its users, not how. You describe system functions (verbs), the functional exchanges between them, and the capability they provide.
Click Add a level… and choose System. Its palette has Actor, Function, Port in, Port out, Mission, Capability, Functional chain and Functional exchange, among others.
- Pick Actor and name it
Occupant. Each level has its own actors. - Pick Function four times:
Measure the room temperature,Take the set temperature,Decide the heating demand,Drive the burner. - Pick Functional exchange, click
Measure the room temperature, thenDecide the heating demand, and name itroom temperature. The workbench declares an output port on the first function and an input port on the second. Drawset temperaturefrom Take to Decide, andheating demandfrom Decide to Drive. - Pick Mission (
Comfort at home) and Capability (Heat the home). A mission is a high-level goal; a capability is what the system can do to serve it. - Pick Functional chain and name it
Reading to burner.
Then add, in the Properties form or the text, the latency of each function and the chain's latency budget:
system_analysis "System Analysis" {
actor "Occupant" { id: "SA-OCCUPANT" }
mission "Comfort at home" { id: "MIS-COMFORT" }
capability "Heat the home" {
id: "CAP-HEAT"
mission: "MIS-COMFORT"
realizes: "OC-WARM"
involves: ["SA-OCCUPANT", "SF-MEASURE", "SF-SET", "SF-DECIDE", "SF-BURN"]
}
function "Measure the room temperature" {
id: "SF-MEASURE"
latency: 500 ms
port out temperature
}
function "Take the set temperature" {
id: "SF-SET"
port out setpoint
}
function "Decide the heating demand" {
id: "SF-DECIDE"
latency: 200 ms
port in temperature
port in setpoint
port out demand
}
function "Drive the burner" {
id: "SF-BURN"
latency: 1 s
port in demand
}
functional_exchange "room temperature" { id: "FE-TEMP" from: "SF-MEASURE.temperature" to: "SF-DECIDE.temperature" }
functional_exchange "set temperature" { id: "FE-SET" from: "SF-SET.setpoint" to: "SF-DECIDE.setpoint" }
functional_exchange "heating demand" { id: "FE-DEMAND" from: "SF-DECIDE.demand" to: "SF-BURN.demand" }
functional_chain "Reading to burner" {
id: "FC-HEAT"
involves: ["SF-MEASURE", "SF-DECIDE", "SF-BURN"]
latency_budget: 2 s
}
}Read the new parts:
latency: 500 msis a typed quantity, not text. The compiler knows it is a duration, and converts1 sand500 msto the same unit.- A functional chain is an ordered path through functions.
involveslists them in order, andlatency_budget: 2 sis the time the path may take. This is requirement REQ-002 written in a form the compiler can evaluate. realizes: "OC-WARM"says this capability realizes the operational capability of step 2. It is the first link between two levels.
Check the model now: in the workbench the findings list is empty, and with the compiler installed on your computer you would see the same:
$ arclang check heating_4.arc
Checking heating_4.arc...
✓ No compilation errors
⚠ Traceability warnings:
Requirement REQ-001 has no trace
Requirement REQ-002 has no trace
Requirement REQ-003 has no traceThe warnings say that no trace reaches the requirements yet. Step 6 fixes that.
Step 4: logical architecture
The logical architecture opens the box. You split the system into logical components, still without choosing technology, and you allocate to each one the functions it performs.
Click Add a level… and choose Logical. Its palette has Component, Function, Port in, Port out, Interface, Component exchange and Functional exchange.
- Pick Component three times:
Thermostat,Heating controller,Burner unit. - Select
Thermostat, pick Function and name itSense the temperature. A function created inside a component is allocated to it: the component performs it. AddShow and set the temperatureto the thermostat,Compute the demandto the controller, andFire the burnerto the burner unit. - Pick Component exchange and draw
Readings and set pointfrom the thermostat to the controller, thenBurner commandfrom the controller to the burner unit. The workbench declares the ports at both ends. - In the Properties form of each logical function, set
realizesto the system function it realizes, and copy its latency.
logical_architecture "Logical Architecture" {
component "Thermostat" {
id: "LC-THERMO"
function "Sense the temperature" { id: "LF-SENSE" realizes: "SF-MEASURE" latency: 500 ms }
function "Show and set the temperature" { id: "LF-SET" realizes: "SF-SET" }
port out readings
}
component "Heating controller" {
id: "LC-CTRL"
function "Compute the demand" { id: "LF-DECIDE" realizes: "SF-DECIDE" latency: 200 ms }
port in readings
port out command
}
component "Burner unit" {
id: "LC-BURNER"
function "Fire the burner" { id: "LF-BURN" realizes: "SF-BURN" latency: 1 s }
port in command
}
component_exchange "Readings and set point" { id: "CE-READINGS" from_port: "LC-THERMO.readings" to_port: "LC-CTRL.readings" }
component_exchange "Burner command" { id: "CE-COMMAND" from_port: "LC-CTRL.command" to_port: "LC-BURNER.command" }
}Each level has functions of its own. Sense the temperature is a logical function that realizes the system function Measure the room temperature; nesting it inside Thermostat is the allocation.
Step 5: physical architecture
The physical architecture says what the system is really made of: nodes (hardware that hosts software), the logical components deployed on them, and the physical links between nodes.
Click Add a level… and choose Physical. Its palette has Node, Behavior component, Hardware component, Function, the port tools, Physical link, Physical exchange and Functional exchange.
- Pick Node twice:
Wall thermostatandBoiler control board. - Pick Physical link, click the two nodes, and name it
Radio link. - In the text, add the
deployslines: the thermostat component runs on the wall thermostat; the controller and the burner unit run on the control board. Add the link's protocol and bandwidth.
physical_architecture "Physical Architecture" {
node "Wall thermostat" {
id: "PN-WALL"
processor: "Low-power microcontroller"
deploys "LC-THERMO"
}
node "Boiler control board" {
id: "PN-BOARD"
deploys "LC-CTRL"
deploys "LC-BURNER"
}
physical_link "Radio link" {
id: "PL-RADIO"
from: "PN-WALL"
to: "PN-BOARD"
protocol: "868 MHz radio"
bandwidth: 20 kbps
}
}deploys must name a logical component that exists: a misspelled id stops the build. bandwidth: 20 kbps is a data rate, another typed quantity.
Step 6: traces
The levels are written. Now link them with traces, so that every element is tied to the reason it exists:
- realizes goes from a lower level to the level above: a system function realizes an operational activity;
- satisfies goes from a component to a requirement.
In the workbench, pick Trace in the palette of a sheet, click the source and the target, and type the kind (realizes is proposed). Traces between levels are easier to type in the text, after all the blocks:
trace "SF-SET" realizes "OA-WISH"
trace "SF-DECIDE" realizes "OA-WARM"
trace "LC-CTRL" satisfies "REQ-001" { rationale: "Closes the loop between the reading and the set point" }
trace "LC-BURNER" satisfies "REQ-002" { rationale: "Fires the burner at the end of the chain" }
trace "LC-THERMO" satisfies "REQ-003" { rationale: "The set point is changed on the thermostat" }rationale is free text for the reviewer: why this component meets this requirement.
$ arclang check heating_7.arc
Checking heating_7.arc...
✓ No compilation errorsEvery requirement is now satisfied by a component, and the check reports no warning.
Step 7: run the production gate
Compiling is not the same as being ready. The production gate answers a different question: is this model complete enough for a design review? Run it:
$ arclang gate heating_7.arc
Production-readiness gate (ISO26262) — heating_7.arc
Requirements: 3 total, 3 satisfied, 0 verified
✗ 3 blocker(s):
[requirements.verification] requirement 'REQ-001' has no verification case (test_case verifies: [...])
[requirements.verification] requirement 'REQ-002' has no verification case (test_case verifies: [...])
[requirements.verification] requirement 'REQ-003' has no verification case (test_case verifies: [...])
PRODUCTION GATE: FAIL ✗ (3 blockers)
Error: Compilation error: production gate failed with 3 blocker(s)The gate fails, with three blockers: the requirements are satisfied, but nothing verifies them. A requirement that nobody checks is a promise, not a requirement.
Step 8: add the test cases
A test_case states how one or more requirements are verified, with a method: test, analysis, inspection or demonstration.
REQ-002 can be verified on the model itself. A constraint is an assertion the compiler evaluates: here, the sum of the latencies along the chain must not exceed its budget. The test case TC-002 is an analysis that asserts this constraint, so its verdict is computed on every build.
constraint "Chain within budget" {
id: "CST-HEAT"
assert: sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget
}
test_case "TC-001" {
verifies: ["REQ-001"]
method: "test"
description: "Record the room temperature for 24 h in a test house"
}
test_case "TC-002" {
verifies: ["REQ-002"]
method: "analysis"
asserts: ["CST-HEAT"]
description: "The latencies along the chain Reading to burner fit its budget"
}
test_case "TC-003" {
verifies: ["REQ-003"]
method: "demonstration"
description: "Change the set point on the thermostat and watch the boiler react"
}Step 9: check and gate
Run the check and the gate again:
$ arclang check heating_8.arc
Checking heating_8.arc...
✓ No compilation errors
Constraints:
✓ Chain within budget — sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget [1.7 s vs 2 s]
$ arclang gate heating_8.arc
Production-readiness gate (ISO26262) — heating_8.arc
Requirements: 3 total, 3 satisfied, 3 verified
Constraints: 1 declared, 1 hold
PRODUCTION GATE: PASS ✓The check evaluates the constraint: 500 ms + 200 ms + 1 s = 1.7 s, within the 2 s budget. The gate passes: three requirements, three satisfied, three verified.
Try breaking it. Change the latency of Drive the burner from 1 s to 1.5 s (here in a copy, slow.arc) and run the gate:
$ arclang gate slow.arc
Production-readiness gate (ISO26262) — slow.arc
Requirements: 3 total, 3 satisfied, 2 verified
Constraints: 1 declared, 0 hold
✗ 3 blocker(s):
[requirements.verification] requirement 'REQ-002': verification cases fail (TC-002)
[timing.budget] functional_chain 'Reading to burner': sum of function latencies 2200.0 ms exceeds the 2000.0 ms budget
[constraints.violated] constraint 'Chain within budget' is violated: `sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget` — left side is 2.2 s, right side is 2 s
PRODUCTION GATE: FAIL ✗ (3 blockers)
Error: Compilation error: production gate failed with 3 blocker(s)The chain now takes 2.2 s. The budget is exceeded, the constraint fails, TC-002 fails with it, and REQ-002 is no longer verified. The model caught a timing problem before any hardware exists.
The complete model
Here is the whole file you built. Every block of it appeared in a step above, in the same order.
model HomeHeating {
name: "Home heating"
version: "0.1"
}
requirements {
req "REQ-001" "Comfort" {
description: "The system shall keep the living room within 0.5 °C of the set temperature"
priority: "High"
}
req "REQ-002" "Reaction time" {
description: "The system shall start or stop the burner within 2 s of a reading that crosses the set temperature"
priority: "Medium"
}
req "REQ-003" "Set temperature" {
description: "The occupant shall be able to change the set temperature on the thermostat"
priority: "Medium"
}
}
operational_analysis "Operational Analysis" {
operational_capability "Live in a warm home" {
id: "OC-WARM"
description: "The home stays at the temperature its occupants want"
involves: ["OA-OCCUPANT", "OA-HOME"]
}
actor "Occupant" {
id: "OA-OCCUPANT"
description: "A person who lives in the home"
}
entity "Home" {
id: "OA-HOME"
description: "The house, its rooms and its boiler"
activity "Take the wished temperature" { id: "OA-WISH" }
activity "Keep the rooms warm" { id: "OA-WARM" }
}
interaction "Wished temperature" { from: "OA-OCCUPANT" to: "OA-WISH" }
interaction "Temperature to reach" { from: "OA-WISH" to: "OA-WARM" }
}
system_analysis "System Analysis" {
actor "Occupant" { id: "SA-OCCUPANT" }
mission "Comfort at home" { id: "MIS-COMFORT" }
capability "Heat the home" {
id: "CAP-HEAT"
mission: "MIS-COMFORT"
realizes: "OC-WARM"
involves: ["SA-OCCUPANT", "SF-MEASURE", "SF-SET", "SF-DECIDE", "SF-BURN"]
}
function "Measure the room temperature" {
id: "SF-MEASURE"
latency: 500 ms
port out temperature
}
function "Take the set temperature" {
id: "SF-SET"
port out setpoint
}
function "Decide the heating demand" {
id: "SF-DECIDE"
latency: 200 ms
port in temperature
port in setpoint
port out demand
}
function "Drive the burner" {
id: "SF-BURN"
latency: 1 s
port in demand
}
functional_exchange "room temperature" { id: "FE-TEMP" from: "SF-MEASURE.temperature" to: "SF-DECIDE.temperature" }
functional_exchange "set temperature" { id: "FE-SET" from: "SF-SET.setpoint" to: "SF-DECIDE.setpoint" }
functional_exchange "heating demand" { id: "FE-DEMAND" from: "SF-DECIDE.demand" to: "SF-BURN.demand" }
functional_chain "Reading to burner" {
id: "FC-HEAT"
involves: ["SF-MEASURE", "SF-DECIDE", "SF-BURN"]
latency_budget: 2 s
}
}
logical_architecture "Logical Architecture" {
component "Thermostat" {
id: "LC-THERMO"
function "Sense the temperature" { id: "LF-SENSE" realizes: "SF-MEASURE" latency: 500 ms }
function "Show and set the temperature" { id: "LF-SET" realizes: "SF-SET" }
port out readings
}
component "Heating controller" {
id: "LC-CTRL"
function "Compute the demand" { id: "LF-DECIDE" realizes: "SF-DECIDE" latency: 200 ms }
port in readings
port out command
}
component "Burner unit" {
id: "LC-BURNER"
function "Fire the burner" { id: "LF-BURN" realizes: "SF-BURN" latency: 1 s }
port in command
}
component_exchange "Readings and set point" { id: "CE-READINGS" from_port: "LC-THERMO.readings" to_port: "LC-CTRL.readings" }
component_exchange "Burner command" { id: "CE-COMMAND" from_port: "LC-CTRL.command" to_port: "LC-BURNER.command" }
}
physical_architecture "Physical Architecture" {
node "Wall thermostat" {
id: "PN-WALL"
processor: "Low-power microcontroller"
deploys "LC-THERMO"
}
node "Boiler control board" {
id: "PN-BOARD"
deploys "LC-CTRL"
deploys "LC-BURNER"
}
physical_link "Radio link" {
id: "PL-RADIO"
from: "PN-WALL"
to: "PN-BOARD"
protocol: "868 MHz radio"
bandwidth: 20 kbps
}
}
trace "SF-SET" realizes "OA-WISH"
trace "SF-DECIDE" realizes "OA-WARM"
trace "LC-CTRL" satisfies "REQ-001" { rationale: "Closes the loop between the reading and the set point" }
trace "LC-BURNER" satisfies "REQ-002" { rationale: "Fires the burner at the end of the chain" }
trace "LC-THERMO" satisfies "REQ-003" { rationale: "The set point is changed on the thermostat" }
constraint "Chain within budget" {
id: "CST-HEAT"
assert: sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget
}
test_case "TC-001" {
verifies: ["REQ-001"]
method: "test"
description: "Record the room temperature for 24 h in a test house"
}
test_case "TC-002" {
verifies: ["REQ-002"]
method: "analysis"
asserts: ["CST-HEAT"]
description: "The latencies along the chain Reading to burner fit its budget"
}
test_case "TC-003" {
verifies: ["REQ-003"]
method: "demonstration"
description: "Change the set point on the thermostat and watch the boiler react"
}You can download it and open it in the playground or in VS Code.
With the compiler installed, draw every view of the model in one self-contained HTML file:
$ arclang diagram heating.arc -f html -o heating.html
✓ oab — 4 nodes, 2 edges
✓ sab — 6 nodes, 3 edges
✓ lab — 7 nodes, 2 edges
✓ pab — 5 nodes, 1 edges
✓ cap — 10 nodes, 9 edges
✓ req — 9 nodes, 6 edges
✓ par — 9 nodes, 4 edges
✓ gv — 10 nodes, 0 edges
✓ iv:Operational Analysis — 2 nodes, 0 edges
✓ afv:System Analysis — 8 nodes, 5 edges
✓ iv:Logical Architecture — 3 nodes, 2 edges
✓ iv:Physical Architecture — 2 nodes, 1 edges
Output: heating.htmlEach line is a view: oab for the operational level, sab for the system, lab for the logical architecture, pab for the physical one, cap for missions and capabilities, req for requirements, par for the constraints, and the views of the SysML v2 standard (gv, iv, afv). Diagrams describes them.
What to do next
- Modeling with Arcadia explains each level of the method in more depth, with the names of the Capella diagrams, and adds the fifth level, the product breakdown, to this model.
- Modeling with SysML v2 maps this model to the concepts of the SysML v2 standard and exports it.
- Getting started installs the compiler on your computer, and the language tour covers the rest of the language: data models, modes and states, scenarios, hazards.