3 · The trigger
A blaster that cannot be fired is a lamp. Carry on from
part 2 — or open Project ▾ ▸
Examples ▸ Tutorial · 02 first light to catch up.
1 · Add the button
Section titled “1 · Add the button”SETUP ▸ Inputs ▸ type trigger into + new input.
| Control | Set it to | Meaning |
|---|---|---|
| type | digital |
a button, not a knob |
| label | TRIGGER |
what the on-screen button says |
| color | #ff3b30 |
what the on-screen button looks like |
| wiring | pin |
it is soldered to a GPIO |
| pin | 14 |
which GPIO |
| activeLow | ticked | button to GND: pressing pulls the pin down |
| pull | up |
enable the internal pull-up |
| hold | blank | the tap/held split in ms; blank means the default, 400 |
What an input does not decide
Section titled “What an input does not decide”It does not decide what a press means. Every digital input emits all four of these, always:
| Event | Fires when |
|---|---|
trigger.pressed |
it went active — the moment of contact |
trigger.released |
it went inactive |
trigger.tapped |
released quickly: a short press, under holdMs (default 400) |
trigger.held |
still down past holdMs: a long press begins |
Your states pick which ones they care about, from a dropdown. The same physical button is a tap in one state and a hold in another, and you write no timer.
2 · Add the firing sequence
Section titled “2 · Add the firing sequence”BEHAVIORS ▸ Scripts ▸ + new script ▸ fire.
Add two set steps to its list (+ action ▸ set, twice) and fill them in:
| Step | device | mode | colour | sec |
|---|---|---|---|---|
| 1 | @barrel |
color | #ffffff |
0.05 |
| 2 | @barrel |
color | #ff2a00 |
0.12 |
A device set row has a color / on-off dropdown, because a device set is
one or the other and never both. Leave it on color.
3 · Give the state machine somewhere to go
Section titled “3 · Give the state machine somewhere to go”Open States. You have Ready from part 2; add Firing with
+ new state.
On the Ready card:
- on enter already has
start▸idlefrom part 2. - on exit —
+ action▸stop, targetidle. - Click
+ when eventand choosetrigger.pressedfrom the list. A row appears: whentrigger.pressed→(stay). Change the second dropdown toFiring.
On the Firing card:
- on enter —
+ action▸run, targetfire. + when event▸fire.done→Ready.
start versus run
Section titled “start versus run”Both begin a script; the difference is what you get back.
start— set it going and forget it. For looks and loops.stopends it.run— set it going, and the engine promises a<script>.doneevent when it finishes. For anything the show has to wait for.
fire.done is what brings the state machine home. Nothing had to know how long
the fire script takes; change its timings and the transition still lands on the
last frame.
A button is enabled by the state machine
Section titled “A button is enabled by the state machine”
There is no separate list of which buttons are live. A button is enabled exactly when the current state has a handler for one of its events, and the Studio dims it when it is not. If you press something and nothing happens, the answer is almost always that the current state has no handler for that event.
Tap to toggle, hold to run
Section titled “Tap to toggle, hold to run”Both patterns come out of the same button and the same two dropdowns:
- Tap to toggle — handle
trigger.tappedin both states, one going each way. The button is momentary; the states remember which side you are on. - Hold to run —
trigger.pressedstarts it,trigger.releasedstops it.
What the editor wrote
Section titled “What the editor wrote”{
// ===========================================================
// BLASTER · STEP 3 — The trigger
// A button, and a state machine with somewhere to go.
// ===========================================================
"schemaVersion": 1,
"name": "Blaster 03 - The Trigger",
"board": "t-display-s3",
"devices": {
"barrel": { "kind": "ledstrip", "count": 8, "pin": 16, "order": "GRB", "brightness": 160 }
},
// An input is the HARDWARE. What a press MEANS is decided by
// the states below, which is why one button can be a tap in
// one state and a hold in another.
"inputs": {
"trigger": { "label": "TRIGGER", "color": "#ff3b30", "pin": 14, "activeLow": true, "pull": "up" }
},
"scripts": {
"idle": {
"children": [
{ "blink": "@barrel", "color": "#ff6a00", "off": "#140400", "sec": 1.4, "loop": true }
]
},
// A one-shot. Two steps, one after the other: a white flash,
// then a red afterglow. `sec` on a `set` HOLDS the colour for
// that long — without it the set lasts a single frame.
"fire": {
"children": [
{ "set": "@barrel", "color": "#ffffff", "sec": 0.05 },
{ "set": "@barrel", "color": "#ff2a00", "sec": 0.12 }
]
}
},
"states": {
"initial": "Ready",
"Ready": {
"enter": [ { "start": "idle" } ],
"exit": [ { "stop": "idle" } ],
"on": { "trigger.pressed": "Firing" }
},
// `run` starts the script AND promises a `fire.done` event
// when it finishes. That event is what brings us home.
"Firing": {
"enter": [ { "run": "fire" } ],
"on": { "fire.done": "Ready" }
}
}
}
Two shapes worth recognising in that file, because the editor draws them differently:
- A handler that is a bare string —
"fire.done": "Ready"— is a pure transition. That is thewhen … → …row with no actions under it. - A handler that is an object —
{ "do": [...], "to": "...", "when": "..." }— has actions, and optionally a guard. Click+ actionson awhenrow and the editor converts one into the other for you.
Next: 4 · Flow — the one idea the rest of the engine is built on.