Skip to content

Session 3: Deployment, Configuration, and Field Maintenance

Synopsis

Covers organizing project files, managing device configuration, updating code safely, and preparing async systems for real-world support scenarios.

Session Content

Session 3: Deployment, Configuration, and Field Maintenance

Session Overview

In this session, learners will move from development to real-world use of a Raspberry Pi Pico 2 W project. The focus is on preparing a device for deployment, configuring it for reliable operation, and performing basic field maintenance tasks such as logging, troubleshooting, and safe updates.

By the end of this session, learners will be able to: - Configure a Pico 2 W for deployment in a stable, repeatable way - Store and load runtime configuration from a file - Add basic device identification and startup diagnostics - Implement resilient networking logic with reconnect behavior - Log operational events for field troubleshooting - Perform safe maintenance tasks such as firmware updates and config changes


Session Duration

Approximately 45 minutes


Prerequisites

  • Raspberry Pi Pico 2 W
  • USB cable
  • Thonny IDE installed
  • MicroPython firmware installed on the Pico 2 W
  • Basic familiarity with Python, GPIO, and Wi-Fi setup
  • A working Wi-Fi network

Development Environment Setup

Thonny Setup

  1. Connect the Pico 2 W to your computer via USB.
  2. Open Thonny.
  3. Go to Tools > Options > Interpreter.
  4. Select:
  5. MicroPython (Raspberry Pi Pico)
  6. Confirm the correct port is selected.
  7. Open the Shell and verify the device responds.

Create a small project structure on the Pico:

  • main.py — application entry point
  • config.json — deployment configuration
  • boot.py — optional boot-time setup
  • logger.py — simple logging helper

Theory: Deployment and Field Maintenance

1. What Deployment Means

Deployment is the step where a development prototype becomes a device that can run unattended. A deployed device should: - Start automatically on power-up - Reconnect to Wi-Fi if needed - Use stored configuration instead of hardcoded values - Provide useful diagnostics for troubleshooting - Be easy to update in the field

2. Why Configuration Matters

Hardcoding values like Wi-Fi credentials, device names, and thresholds makes maintenance difficult. A configuration file allows changes without editing code.

Typical configuration values: - Device name - Wi-Fi SSID and password - Reporting interval - Sensor thresholds - Server endpoint

3. Field Maintenance Goals

A maintainable device should support: - Log messages for failures and status - Safe file-based configuration changes - Recovery after reboot - Minimal manual intervention - Clear separation between code and data

4. Common Deployment Issues

  • Incorrect Wi-Fi credentials
  • Missing or corrupted config files
  • Weak signal or unreliable network
  • Unexpected resets due to memory use
  • Inconsistent startup behavior

Hands-On Exercise 1: Create a Configuration File

Goal

Store deployment settings in a JSON file and load them at startup.

config.json

Create this file on the Pico:

{
  "device_name": "pico2w-field-node",
  "wifi_ssid": "YOUR_WIFI_NAME",
  "wifi_password": "YOUR_WIFI_PASSWORD",
  "report_interval_s": 30,
  "debug": true
}

Hands-On Exercise 2: Load Configuration Safely

Goal

Read configuration from a file, fall back to defaults if needed, and print startup diagnostics.

main.py

# main.py
# Load deployment configuration safely and show startup diagnostics.

import json
import time
import machine
import os

CONFIG_FILE = "config.json"

DEFAULT_CONFIG = {
    "device_name": "pico2w-default",
    "wifi_ssid": "",
    "wifi_password": "",
    "report_interval_s": 60,
    "debug": False
}


def load_config():
    """
    Load configuration from config.json.
    If the file is missing or invalid, return default settings.
    """
    config = DEFAULT_CONFIG.copy()

    try:
        with open(CONFIG_FILE, "r") as f:
            user_config = json.load(f)
            config.update(user_config)
        print("Config loaded successfully.")
    except OSError:
        print("Config file not found. Using defaults.")
    except ValueError:
        print("Config file is invalid JSON. Using defaults.")

    return config


def print_system_info(config):
    """
    Print basic diagnostic information useful for field maintenance.
    """
    print("\n=== Device Startup ===")
    print("Device name:", config["device_name"])
    print("Machine unique ID:", machine.unique_id())
    print("Free memory:", machine.mem_free(), "bytes")

    try:
        stat = os.statvfs("/")
        free_space = stat[0] * stat[3]
        print("Approx free storage:", free_space, "bytes")
    except:
        print("Storage information unavailable.")

    print("======================\n")


def main():
    config = load_config()
    print_system_info(config)

    # Keep the program alive for testing.
    while True:
        print("Device running...")
        time.sleep(config["report_interval_s"])


main()

Expected Output

Config loaded successfully.

=== Device Startup ===
Device name: pico2w-field-node
Machine unique ID: b'\x00\x00\x00...'
Free memory: 120000 bytes
Approx free storage: 3000000 bytes
======================

Device running...
Device running...

Hands-On Exercise 3: Add a Simple Logger

Goal

Create a reusable logger that prints messages with timestamps and writes them to a file.

logger.py

# logger.py
# Simple file-based logger for field debugging.

import time

LOG_FILE = "device.log"


def _timestamp():
    """
    Return a simple elapsed-time timestamp string.
    """
    t = time.localtime()
    return "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(
        t[0], t[1], t[2], t[3], t[4], t[5]
    )


def log(message, level="INFO"):
    """
    Print a log message and append it to device.log.
    """
    line = "[{}] {}: {}".format(_timestamp(), level, message)
    print(line)

    try:
        with open(LOG_FILE, "a") as f:
            f.write(line + "\n")
    except OSError:
        print("Warning: log file could not be written.")

Updated main.py

import time
import json
import machine
from logger import log

CONFIG_FILE = "config.json"

DEFAULT_CONFIG = {
    "device_name": "pico2w-default",
    "wifi_ssid": "",
    "wifi_password": "",
    "report_interval_s": 60,
    "debug": False
}


def load_config():
    config = DEFAULT_CONFIG.copy()
    try:
        with open(CONFIG_FILE, "r") as f:
            config.update(json.load(f))
        log("Configuration loaded.")
    except OSError:
        log("Config file missing, using defaults.", "WARN")
    except ValueError:
        log("Config file invalid, using defaults.", "WARN")
    return config


def main():
    config = load_config()
    log("Device started: {}".format(config["device_name"]))
    log("Unique ID: {}".format(machine.unique_id()))

    while True:
        log("Heartbeat")
        time.sleep(config["report_interval_s"])


main()

Example Output

[2026-03-22 10:15:00] INFO: Configuration loaded.
[2026-03-22 10:15:00] INFO: Device started: pico2w-field-node
[2026-03-22 10:15:00] INFO: Unique ID: b'\x00\x00\x00...'
[2026-03-22 10:15:00] INFO: Heartbeat

Hands-On Exercise 4: Wi-Fi Connection with Retry Logic

Goal

Connect to Wi-Fi reliably with retries and logging.

main.py

import time
import network
import json
from logger import log

CONFIG_FILE = "config.json"


def load_config():
    with open(CONFIG_FILE, "r") as f:
        return json.load(f)


def connect_wifi(ssid, password, max_retries=10):
    """
    Connect to Wi-Fi with retry logic.
    """
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    if wlan.isconnected():
        log("Already connected to Wi-Fi.")
        return wlan

    log("Connecting to Wi-Fi SSID '{}'...".format(ssid))

    wlan.connect(ssid, password)

    retries = 0
    while not wlan.isconnected() and retries < max_retries:
        log("Waiting for connection... attempt {}".format(retries + 1))
        time.sleep(1)
        retries += 1

    if wlan.isconnected():
        log("Wi-Fi connected: {}".format(wlan.ifconfig()[0]))
        return wlan
    else:
        log("Wi-Fi connection failed.", "ERROR")
        return None


def main():
    config = load_config()
    wlan = connect_wifi(
        config["wifi_ssid"],
        config["wifi_password"]
    )

    if wlan is None:
        log("Continuing in offline mode.", "WARN")
    else:
        log("Online services can now start.")

    while True:
        log("Main loop active")
        time.sleep(config.get("report_interval_s", 30))


main()

Expected Output

[2026-03-22 10:20:00] INFO: Connecting to Wi-Fi SSID 'HomeNetwork'...
[2026-03-22 10:20:01] INFO: Waiting for connection... attempt 1
[2026-03-22 10:20:02] INFO: Waiting for connection... attempt 2
[2026-03-22 10:20:03] INFO: Wi-Fi connected: 192.168.1.50
[2026-03-22 10:20:03] INFO: Online services can now start.

Hands-On Exercise 5: Add a Startup LED Status Indicator

Goal

Use the onboard LED to signal startup and network status.

main.py

import time
import network
import machine
import json

LED = machine.Pin("LED", machine.Pin.OUT)


def blink(times, on_time=0.2, off_time=0.2):
    """
    Blink the onboard LED a given number of times.
    """
    for _ in range(times):
        LED.value(1)
        time.sleep(on_time)
        LED.value(0)
        time.sleep(off_time)


def load_config():
    with open("config.json", "r") as f:
        return json.load(f)


def connect_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)

    for _ in range(10):
        if wlan.isconnected():
            return wlan
        blink(1, 0.1, 0.1)
        time.sleep(1)

    return None


def main():
    blink(3, 0.1, 0.1)  # startup indication

    config = load_config()
    wlan = connect_wifi(config["wifi_ssid"], config["wifi_password"])

    if wlan:
        LED.value(1)  # solid on for connected
    else:
        blink(5, 0.05, 0.05)  # error indication

    while True:
        time.sleep(5)


main()

Field Maintenance Tasks

1. Updating Configuration

If Wi-Fi changes, edit only config.json rather than code. Keep usernames, passwords, and endpoints outside application logic.

2. Reviewing Logs

Open device.log in Thonny or download it to inspect: - Boot sequence - Wi-Fi status - Error messages - Unexpected resets

3. Restarting the Device

A simple reboot can resolve temporary connection or memory issues:

import machine
machine.reset()

4. Checking Free Memory

Useful during troubleshooting:

import machine
print(machine.mem_free())

5. Checking Files on the Device

List files using:

import os
print(os.listdir())

Best Practices for Deployment

  • Keep main.py small and stable
  • Store settings in a separate config file
  • Log major startup and failure events
  • Retry network connections before failing
  • Use the LED for simple operational status
  • Avoid long blocking operations where possible
  • Test reboot behavior before field deployment

Common Troubleshooting Checklist

  • Confirm config.json is valid JSON
  • Verify Wi-Fi credentials
  • Check signal strength near the deployment location
  • Inspect device.log
  • Ensure main.py does not crash at startup
  • Re-flash MicroPython if the filesystem becomes unstable
  • Restart the board after updates

Mini Challenge

Modify the logger and configuration file so that: 1. The device name appears in every log line 2. The report interval can be changed without editing main.py 3. The LED blinks twice when Wi-Fi connects successfully


Review Questions

  1. Why is storing configuration in a file better than hardcoding it?
  2. What should a deployed IoT device do after a reboot?
  3. Why are logs important for field maintenance?
  4. What is the purpose of Wi-Fi retry logic?
  5. How can the onboard LED help during deployment?

Summary

In this session, learners built the foundations for a maintainable Pico 2 W deployment: - Loaded configuration from a file - Added logging for troubleshooting - Implemented Wi-Fi reconnect behavior - Used the onboard LED for status indication - Learned practical field maintenance techniques


Suggested Next Steps

  • Add a sensor reading to the main loop
  • Send logged data to a cloud endpoint
  • Implement MQTT publishing
  • Add an HTTP configuration interface
  • Store logs with rotation to prevent storage exhaustion

Back to Chapter | Back to Master Plan | Previous Session | Next Session