scale.fr
to scale with percent, max [
here bounded is clamp: percent, 0, 100;
bounded * max / 100
]
scale: 80, 255
to blink-pin with pin, count, delay [
repeat count [
gpio.high: pin; wait: delay;
gpio.low: pin; wait: delay
]
]
blink-pin: $led_builtin, 5, 200
to watch.light [
here level is adc.read: $a0;
if level > 2000 [
led.on:
] else [
led.off:
]
]
watch.light:
to signal [ led.blink: 2, 120 ]
to warn [ signal: ]
to signal [ led.blink: 8, 40 ]
warn:
boot is fn [
on $boot_button falling debounce 30 [
led.toggle:
]
]
save
to doze [
sleep.wake-on-gpio: $boot_button, 0;
sleep.deep: 30000
]
doze:
blink.rate is 200
to pulse [ led.blink: 3, blink.rate ]
save -- snapshot the overlay to flash
blink.rate is 40 -- try a faster rate, live...
restore -- ...snap back to the saved 200
to on-drop [ on wifi.disconnected [ wifi.connect: ] ]
to on-back [ on wifi.reconnected [ led.blink: 1, 40 ] ]
boot is fn [ on-drop:; on-back: ]
save
boot is fn [
watchdog.arm: 8000;
every 2000 [ watchdog.feed: ]
]
save
notes
A word is a name with code behind it — write it with to, run it with a trailing colon. with names its arguments.
here binds a value that's local to the block, so bounded exists only inside scale. A block hands back its last line — here, the scaled number.
This example blinks an LED. The base image has the same word, named blink. repeat runs the block a fixed number of times, wait: waits, and $led_builtin is the pin of the built-in LED. Where it is nil, use the pin of your own LED.
Arguments follow the colon, comma-separated: a pin, a count, a delay.
Read an analog pin with adc.read:, keep the reading in a local, and branch on it. if … else takes the first block when the test is true.
Sense, decide, act — the whole loop of physical computing, in seven lines.
warn calls signal by name. Redefine signal and every caller picks up the new version on the next call.
No recompile, no re-flash, nothing to restart. This is the thing Frothy is really for: changing a running device one word at a time.
on $boot_button falling debounce 30 [ … ] toggles the LED on a falling edge — a button press — with 30 ms of debounce, so one push registers once. every <ms> [ … ] does the same on a timer.
Handlers fire cooperatively, at safe points between steps — never mid-write. Register them in boot, save, and the device reacts on its own from power-on.
Arm a wake source, then power the core down. sleep.wake-on-gpio: says "come back when this pin goes low"; sleep.deep: sleeps up to that many milliseconds.
The thing a battery project lives or dies on — one line each.
Your top-level names — blink.rate and pulse here — sit in an overlay on top of the base image the board shipped with. save writes that overlay to flash, so it survives a power cycle.
restore snaps the live overlay back to the last save — a cheap undo for a session that wandered. dangerous.wipe erases the saved overlay entirely, back to the bare base. Save a state worth keeping; restore to recover; wipe to start clean.
on wifi.disconnected and on wifi.reconnected are events like any other. The device watches its own link, calls wifi.connect: to bring it back, and blinks once when it's online again. One word holds one event body, so each event gets its own small word, and boot calls both.
watchdog.arm: 8000 means "reset the board if I go quiet for 8 seconds." A timer feeds it every 2 s while things are healthy.
If the code ever wedges and the feeds stop, the watchdog reboots the device on its own — the embedded world's dead-man's switch.