How do I toggle an LED with a button?
Wire a button to an input pin, watch transitions, drive the onboard LED
The smallest hardware-interactive recipe: press a button, the onboard LED toggles. Demonstrates GPIO input monitoring + GPIO output + a tiny piece of script-side state.
Wiring (Pico W)
Wire one leg of a momentary button to GP20, the other leg to GND. The script enables the internal pull-up, so an unpressed button reads high and a pressed button reads low.
The LED is the onboard one — virtual pin 99, no wiring needed.
devicesdk.ts
import { defineConfig } from "@devicesdk/cli";
export default defineConfig({
projectId: "button-led",
devices: {
main: {
className: "ButtonToggle",
main: "./src/devices/main.ts",
deviceType: "pico-w",
wifi: { ssid: "YOUR_WIFI_SSID", password: "YOUR_WIFI_PASSWORD" },
},
},
});
src/devices/main.ts
import { DeviceEntrypoint, OnboardLED, type DeviceResponse } from "@devicesdk/core";
const BUTTON_PIN = 20;
export class ButtonToggle extends DeviceEntrypoint {
async onDeviceConnect() {
// Subscribe to button presses with the internal pull-up enabled.
await this.env.DEVICE.configureGpioInputMonitoring(BUTTON_PIN, true, "up");
// Restore last LED state after a reboot.
const last = await this.env.DEVICE.kv.get<"high" | "low">("led");
await this.env.DEVICE.setGpioState(OnboardLED, last ?? "low");
}
async onMessage(message: DeviceResponse) {
if (message.type !== "gpio_state_changed") return;
if (message.payload.pin !== BUTTON_PIN) return;
// Pull-up: pressed = "low", released = "high". React on press only.
if (message.payload.state !== "low") return;
const current = (await this.env.DEVICE.kv.get<"high" | "low">("led")) ?? "low";
const next = current === "low" ? "high" : "low";
await this.env.DEVICE.setGpioState(OnboardLED, next);
await this.env.DEVICE.kv.put("led", next);
}
}
What this demonstrates
configureGpioInputMonitoringfor hardware-driven events (no polling).OnboardLEDconstant for portable LED code across Pico W, Pico 2 W, ESP32 variants.- Persisting state with
this.env.DEVICE.kvso a reboot doesn’t reset the LED. - Narrowing on
message.type === "gpio_state_changed"inonMessage.
Common gotchas
- Switch bouncing. A mechanical button can fire many
gpio_state_changedevents on a single press. If you see double-toggles, debounce in script: track the last-event timestamp inkvand ignore events within ~50 ms of the previous one. - Wrong pull direction. If you wired the button between 3V3 and the pin (instead of GND), use
pull: "down"and react onstate === "high".
Going further
- Replace the LED with a relay for a real power switch. See the Home Assistant recipe to surface this as an HA
switchentity. - Drive a WS2812 strip color instead — see the WS2812 recipe.