Pepela Smart Home — Thermostat Showcase

This showcase demonstrates kUML’s executable behaviour runtime using a smart-home thermostat as the modelling domain. The same model that produces renderable diagrams can also be run via kuml simulate.

Both scripts live in kuml-examples/src/main/kotlin/dev/kuml/examples/pepela/.

State Machine: Temperature Control Logic

The thermostat’s heating/cooling control is captured as a five-state machine:

  • Off — relays shut down; transitions to Idle on powerOn

  • Idle — monitoring; transitions to Heating, Cooling, or Eco depending on temperature and explicit triggers

  • Heatingrelay.heat(true); returns to Idle once target reached

  • Coolingrelay.cool(true); returns to Idle once target reached

  • Eco — low-power mode at 18 °C setpoint; returns to Idle on normalMode

Temperature-based transitions use tick events that carry integer °C values in their JSON payload. The kUML OCL subset operates on integer arithmetic, so the 1 °C hysteresis band is expressed as:

// (excerpt — see full script in kuml-examples/pepela/)
sysml2Model("Thermostat") {
    val idle    = stateDef("Idle",    entryAction = "display.show('idle')")
    val heating = stateDef("Heating", entryAction = "relay.heat(true)",
                                      exitAction  = "relay.heat(false)")
    val cooling = stateDef("Cooling", entryAction = "relay.cool(true)",
                                      exitAction  = "relay.cool(false)")

    transition("startHeating", idle, heating,
        trigger = "tick",
        guard   = "event.temperature < event.targetTemperature - 1")

    transition("heatDone", heating, idle,
        trigger = "tick",
        guard   = "event.temperature >= event.targetTemperature")
    // ... cooling, eco, power transitions follow the same pattern
}

Run it:

kuml simulate pepela-thermostat-stm.kuml.kts \
  --events pepela-thermostat-stm-events.json \
  --out /tmp/thermostat-trace.json

The events file drives the full heating → idle → cooling → eco → idle → off cycle:

{ "schema": "kuml.events.v1", "events": [
    { "name": "powerOn" },
    { "name": "tick", "payload": { "temperature": 16, "targetTemperature": 21 } },
    { "name": "tick", "payload": { "temperature": 21, "targetTemperature": 21 } },
    { "name": "tick", "payload": { "temperature": 24, "targetTemperature": 21 } },
    { "name": "tick", "payload": { "temperature": 21, "targetTemperature": 21 } },
    { "name": "ecoMode" },
    { "name": "normalMode" },
    { "name": "powerOff" }
] }

Expected state sequence in the trace: Off → Idle → Heating → Idle → Cooling → Idle → Eco → Idle → Off

Activity Diagram: Boot Sequence

The thermostat’s power-on self-test and sensor calibration are captured as an activity diagram. The happy path fans out in parallel via Fork/Join:

// (excerpt — see full script in kuml-examples/pepela/)
sysml2Model("ThermostatBoot") {
    val readSensors    = actionDef("ReadSensors",    action = "sensors.readAll()")
    val decide         = decisionNode("sensors valid?")
    val calibrate      = actionDef("Calibrate",      action = "calibration.run()")
    val fork           = forkNode("bootFork")
    val updateDisplay  = actionDef("UpdateDisplay",  action = "display.update()")
    val logReady       = actionDef("LogReady",       action = "log.info('Thermostat ready')")
    val join           = joinNode("bootJoin")

    controlFlow("validPath",  decide, calibrate,    guard = "sensorsValid")
    controlFlow("toFork",     calibrate, fork)
    controlFlow("forkToDisplay", fork, updateDisplay)
    controlFlow("forkToLog",  fork, logReady)
    controlFlow("displayToJoin", updateDisplay, join)
    controlFlow("logToJoin",  logReady, join)
    // ...
}

Run it:

kuml simulate pepela-thermostat-flow.kuml.kts \
  --events pepela-thermostat-flow-events.json \
  --out /tmp/thermostat-flow-trace.json

The trace will contain ForkSplit, JoinReached, and finally ActivityTerminated.

Try It Yourself

Both scripts and their companion event files are in:

kuml-examples/src/main/kotlin/dev/kuml/examples/pepela/
  pepela-thermostat-stm.kuml.kts        ← state machine
  pepela-thermostat-stm-events.json     ← 16-event sequence
  pepela-thermostat-flow.kuml.kts       ← boot activity
  pepela-thermostat-flow-events.json    ← single start event

The CLI smoke tests (6 tests in PepelaThermostatSmokeTest) run as part of ./gradlew :kuml-cli:check and verify both the state sequence and ActivityTerminated are reached on every build.