> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a device on a Raspberry Pi Pico W

> Flash MicroPython, copy the single-file client, and let the agent switch the onboard LED and read the board temperature

You'll build a [device](/concepts/devices) on a Raspberry Pi Pico W that the agent can ask for its temperature and whose LED it can switch and blink, over Wi-Fi and MQTT, with no extra hardware. You need a Pico W (the W variant has Wi-Fi), a data-capable micro-USB cable, a 2.4 GHz network, Python 3.8+ on your computer, and an agent created with `lua init`.

*Verified against lua-cli 3.45.0.*

## What you'll build

* A Pico W running the [MicroPython client](/devices/micropython-client) as `pico-sensor`.
* Four commands: `led_on`, `led_off`, `blink`, and `read_temperature`.
* Credentials kept in a `config.py` on the board, out of version control.

<Steps>
  <Step title="Flash MicroPython">
    Hold the **BOOTSEL** button while plugging the Pico in; it mounts as a USB drive. Download the Pico W `.uf2` from [micropython.org](https://micropython.org/download/RPI_PICO_W/) and copy it onto the drive. The board reboots into MicroPython.
  </Step>

  <Step title="Copy the client to the board">
    The client ships inside the `lua-device-client` package on PyPI. Install it and `mpremote` on your computer, then copy the file to the board as `lua_device.py`.

    ```bash theme={null}
    pip install lua-device-client mpremote
    python -c "import lua_device, os; print(os.path.join(os.path.dirname(lua_device.__file__), 'micropython.py'))"
    mpremote cp <printed-path> :lua_device.py
    ```
  </Step>

  <Step title="Create the credential and config">
    From the agent project, issue a [device credential](/devices/credentials) for the device name `pico-sensor`. It needs an email sign-in (`lua auth configure`, then choose Email) and grants `commands` and `triggers` by default.

    ```bash theme={null}
    mkdir -p ../pico
    lua devices credential --device-name pico-sensor --out ../pico/.env.device
    ```

    Copy `LUA_AGENT_ID` and `LUA_DEVICE_CREDENTIAL` from that file into a `config.py` beside your Wi-Fi settings, then copy it to the board; keep both files out of version control.

    ```python config.py theme={null}
    WIFI_SSID = "workshop"
    WIFI_PASSWORD = "<wifi-password>"
    AGENT_ID = "<LUA_AGENT_ID from .env.device>"
    DEVICE_CREDENTIAL = "<LUA_DEVICE_CREDENTIAL from .env.device>"
    ```

    ```bash theme={null}
    mpremote cp config.py :config.py
    ```
  </Step>

  <Step title="Write main.py">
    `main.py` runs at boot. The client joins Wi-Fi from the constructor arguments, and `run()` blocks while handling commands and heartbeats.

    ```python main.py theme={null}
    import machine
    import time

    import config
    from lua_device import LuaDevice

    led = machine.Pin("LED", machine.Pin.OUT)
    adc = machine.ADC(4)  # internal temperature sensor


    def read_temperature():
        voltage = adc.read_u16() * 3.3 / 65535
        return round(27 - (voltage - 0.706) / 0.001721, 1)


    device = LuaDevice(
        agent_id=config.AGENT_ID,
        api_key=config.DEVICE_CREDENTIAL,
        device_name="pico-sensor",
        wifi_ssid=config.WIFI_SSID,
        wifi_password=config.WIFI_PASSWORD,
    )


    @device.command("led_on", description="Turn the onboard LED on")
    def led_on(payload):
        led.value(1)
        return {"led": "on"}


    @device.command("led_off", description="Turn the onboard LED off")
    def led_off(payload):
        led.value(0)
        return {"led": "off"}


    @device.command(
        "blink",
        description="Blink the onboard LED a number of times",
        inputSchema={"type": "object", "properties": {"times": {"type": "integer", "minimum": 1, "maximum": 20}}},
    )
    def blink(payload):
        times = int(payload.get("times", 3))
        for _ in range(times):
            led.value(1)
            time.sleep_ms(200)
            led.value(0)
            time.sleep_ms(200)
        return {"blinked": times}


    @device.command("read_temperature", description="Read the board temperature in celsius")
    def cmd_read_temperature(payload):
        return {"temperature": read_temperature(), "unit": "celsius"}


    device.run()
    ```

    ```bash theme={null}
    mpremote cp main.py :main.py
    ```
  </Step>

  <Step title="Run it">
    Start the program with the REPL attached so you can read the client's log lines; after this, it also runs on every power-up.

    ```bash theme={null}
    mpremote run main.py
    ```

    The client logs these lines as it connects; the text comes from the client source, not from a recorded run.

    ```text Client log lines theme={null}
    [lua-device] Connecting to WiFi workshop... OK! 192.168.1.42
    [lua-device] Connecting via WebSocket to wss://mqtt.heylua.ai:443...
    [lua-device] WebSocket connected
    [lua-device] Connected as pico-sensor
    [lua-device] Listening for commands...
    [lua-device] Server confirmed connection: Connected to device gateway (MQTT)
    ```

    `MQTT CONNACK error: rc=5` means the credential, agent ID, or device name don't match the binding, or the credential is a scoped API key from the dashboard rather than a device credential.
  </Step>

  <Step title="Ask the agent">
    From the agent project, drive the board. The model calls `device__pico_sensor__blink` with `{ "times": 5 }` and `device__pico_sensor__read_temperature`.

    ```bash theme={null}
    lua chat -m "Blink the LED on pico-sensor five times, then tell me its temperature"
    ```
  </Step>
</Steps>

<Check>
  `lua devices status --device-name pico-sensor --ci` prints `🟢 Device 'pico-sensor' is online`, and `lua devices test --device-name pico-sensor --command read_temperature --ci` prints the board's reading. Unplug the board: once the broker stops hearing from it, within 1.5 times the 60-second MQTT keep-alive, it publishes the device's will and the state changes to `offline`.
</Check>

Leave the board connected for more than 24 hours and the agent loses the four command tools until the board reconnects: the platform keeps a device's command list for 24 hours from connect, and heartbeats don't extend it. `run()` reconnects only after an error, so power-cycle the board daily or replace `run()` with a loop of your own that calls `disconnect()` and `connect()` before the day is up.

## What you learned

* The MicroPython client is one file copied from the PyPI package. See [MicroPython device client](/devices/micropython-client).
* Commands declared with `@device.command` become tools while the board is connected; an `inputSchema` tells the model what to send. See [Expose device commands as tools](/devices/commands-and-tools).
* The board talks MQTT over WebSocket on port 443, so it works behind most firewalls. See [MQTT protocol](/devices/mqtt-protocol).

## Next steps

<Columns cols={2}>
  <Card title="Factory monitor" href="/devices/examples/industrial-sensor">Add sensors and fire triggers from the Pico W.</Card>
  <Card title="Handle device triggers" href="/devices/triggers">Run code on the platform when the board reports an event.</Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.