Wireless Raspberry Pi Status Monitor using a Pi Pico W and GOOD VIBES



Raspberry Pi Pico W System Monitor: Tracking Pi 400 and Pi 4 Stats

Welcome to this exciting project where we turn a Raspberry Pi Pico W into a system monitor for a Raspberry Pi 400 and Pi 4, displaying their stats on dual 16x2 LCDs! This tutorial walks you through the setup, code, and auto-boot configuration, pairing perfectly with my video—watch it for a visual walkthrough!

What You’ll Need

  • Raspberry Pi Pico W (with MicroPython installed)
  • Raspberry Pi 400 (running Raspbian)
  • Raspberry Pi 4 (running Raspbian)
  • Two 16x2 LCDs with I2C backpacks (address 0x27)
  • Jumper wires for connections
  • WiFi network (Pico W needs a static IP, set via your router)

Project Overview

The Pico W acts as an HTTP server with a static IP (you’ll choose this), receiving stats from the Pi 400 and Pi 4 (on dynamic IPs) every 10 seconds. It displays four vital statistics, cycling every 2 seconds on each LCD’s bottom line:

  • Uptime (DD:HH:MM:SS)
  • CPU Usage (%)
  • Memory Usage (%)
  • Disk Usage (%)

Top LCD shows Pi 400 stats, Bottom LCD shows Pi 4 stats.

Step 1: Hardware Setup

Connect the Pico W to the LCDs:

  • Top LCD (I2C0): SCL to GP1 (Pin 2), SDA to GP0 (Pin 1), VCC to 3.3V (Pin 36), GND to GND (Pin 38)
  • Bottom LCD (I2C1): SCL to GP27 (Pin 32), SDA to GP26 (Pin 31), VCC to 3.3V (Pin 36), GND to GND (Pin 38)
  • Power: Pico W via USB to 5V (VBUS, Pin 40)

Plug the Pi 400 and Pi 4 into power and ensure all devices are on the same WiFi network. The Pico W needs a static IP—choose one (e.g., 192.168.1.100) and reserve it in your router for the Pico W’s MAC address (find it in Step 2).

Step 2: Pico W Code

First, install the LCD libraries (only external ones needed):

  • Download pico_i2c_lcd.py and lcd_api.py from GitHub.
  • In Thonny, connect to your Pico W, go to File > Open > Raspberry Pi Pico, and save both files there.

Here’s the Pico W code (assuming MicroPython is installed):

# pico_w_monitor_static_stats.py
from machine import I2C, Pin
from pico_i2c_lcd import I2cLcd
import network
import usocket as socket
import utime
import ujson

# Hardware Setup
TOP_LCD_ADDR = 0x27
i2c0 = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
top_lcd = I2cLcd(i2c0, TOP_LCD_ADDR, 2, 16)

BOTTOM_LCD_ADDR = 0x27
i2c1 = I2C(1, scl=Pin(27), sda=Pin(26), freq=400000)
bottom_lcd = I2cLcd(i2c1, BOTTOM_LCD_ADDR, 2, 16)

# Static IP - Change these to your chosen IP, gateway, etc.
STATIC_IP = "192.168.1.100"  # Set your static IP here
NETMASK = "255.255.255.0"
GATEWAY = "192.168.1.1"      # Your router’s IP
DNS = "192.168.1.1"          # Usually same as gateway

# WiFi Credentials
try:
    from secrets import wifi_ssid, wifi_password
except ImportError:
    wifi_ssid = "YourWiFiSSID"
    wifi_password = "YourPassword"

# Globals
pi400_uptime = "N/A"
pi400_cpu = 0.0
pi400_mem = 0.0
pi400_disk = 0.0
pi400_last_update = 0
pi4_uptime = "N/A"
pi4_cpu = 0.0
pi4_mem = 0.0
pi4_disk = 0.0
pi4_last_update = 0
display_mode = 0  # 0=uptime, 1=CPU, 2=mem, 3=disk
last_switch = 0

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.ifconfig((STATIC_IP, NETMASK, GATEWAY, DNS))
    top_lcd.clear()
    top_lcd.putstr("WIFI CONN")
    bottom_lcd.clear()
    bottom_lcd.putstr("IP: " + STATIC_IP)
    wlan.connect(wifi_ssid, wifi_password)
    for _ in range(10):
        if wlan.isconnected():
            assigned_ip = wlan.ifconfig()[0]
            print(f"Connected, IP: {assigned_ip}")
            print(f"MAC: {wlan.config('mac')}")  # Log this for router reservation
            top_lcd.clear()
            top_lcd.putstr("WIFI OK")
            bottom_lcd.clear()
            bottom_lcd.putstr("IP: " + assigned_ip)
            utime.sleep(2)
            return wlan, assigned_ip
        utime.sleep(1)
    print("WiFi connection failed")
    top_lcd.clear()
    top_lcd.putstr("WIFI FAIL")
    bottom_lcd.clear()
    bottom_lcd.putstr("CHECK SETUP")
    return wlan, None

def read_full_request(client_socket):
    request = b""
    while True:
        chunk = client_socket.recv(1024)
        if not chunk:
            break
        request += chunk
        if b"\r\n\r\n" in request:
            header_end = request.find(b"\r\n\r\n") + 4
            headers = request[:header_end].decode()
            content_length = 0
            for line in headers.split("\r\n"):
                if line.lower().startswith("content-length:"):
                    content_length = int(line.split(":")[1].strip())
                    break
            if len(request) >= header_end + content_length:
                break
        utime.sleep_ms(10)
    return request

def parse_post_request(request):
    try:
        body_start = request.find(b"\r\n\r\n") + 4
        if body_start == 3:
            print("No body found")
            return None, None, None, None, None
        body = request[body_start:].decode()
        data = ujson.loads(body)
        device = data.get("device")
        uptime = data.get("uptime")
        cpu = data.get("cpu")
        mem = data.get("mem")
        disk = data.get("disk")
        print(f"Parsed - Device: {device}, Uptime: {uptime}, CPU: {cpu}, Mem: {mem}, Disk: {disk}")
        return device, uptime, cpu, mem, disk
    except Exception as e:
        print(f"Parse error: {e}")
        return None, None, None, None, None

def update_display():
    global display_mode, last_switch
    current_time = utime.ticks_ms()
    if utime.ticks_diff(current_time, last_switch) > 2000:
        display_mode = (display_mode + 1) % 4
        last_switch = current_time
    
    pi400_up = pi400_uptime if utime.ticks_diff(current_time, pi400_last_update) > -30000 else "N/A"
    pi400_cp = f"{pi400_cpu:.1f}%" if utime.ticks_diff(current_time, pi400_last_update) > -30000 else "N/A"
    pi400_mm = f"{pi400_mem:.1f}%" if utime.ticks_diff(current_time, pi400_last_update) > -30000 else "N/A"
    pi400_ds = f"{pi400_disk:.1f}%" if utime.ticks_diff(current_time, pi400_last_update) > -30000 else "N/A"
    
    pi4_up = pi4_uptime if utime.ticks_diff(current_time, pi4_last_update) > -30000 else "N/A"
    pi4_cp = f"{pi4_cpu:.1f}%" if utime.ticks_diff(current_time, pi4_last_update) > -30000 else "N/A"
    pi4_mm = f"{pi4_mem:.1f}%" if utime.ticks_diff(current_time, pi4_last_update) > -30000 else "N/A"
    pi4_ds = f"{pi4_disk:.1f}%" if utime.ticks_diff(current_time, pi4_last_update) > -30000 else "N/A"
    
    top_lcd.clear()
    top_lcd.putstr("R-Pi 400")
    top_lcd.move_to(0, 1)
    if display_mode == 0:
        top_lcd.putstr(f"UP: {pi400_up}")
    elif display_mode == 1:
        top_lcd.putstr(f"CPU: {pi400_cp}")
    elif display_mode == 2:
        top_lcd.putstr(f"MEM: {pi400_mm}")
    elif display_mode == 3:
        top_lcd.putstr(f"DSK: {pi400_ds}")
    
    bottom_lcd.clear()
    bottom_lcd.putstr("R-Pi 4")
    bottom_lcd.move_to(0, 1)
    if display_mode == 0:
        top_lcd.putstr(f"UP: {pi4_up}")
    elif display_mode == 1:
        bottom_lcd.putstr(f"CPU: {pi4_cp}")
    elif display_mode == 2:
        bottom_lcd.putstr(f"MEM: {pi4_mm}")
    elif display_mode == 3:
        bottom_lcd.putstr(f"DSK: {pi4_ds}")

# Main
top_lcd.backlight_on()
bottom_lcd.backlight_on()

wlan, assigned_ip = connect_wifi()
if not assigned_ip:
    while True:
        utime.sleep(1)

print(f"Server running at {assigned_ip}:80")

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("", 80))
server_socket.listen(5)

update_display()

while True:
    try:
        client_socket, addr = server_socket.accept()
        request = read_full_request(client_socket)
        
        if b"POST /uptime" in request:
            device, uptime, cpu, mem, disk = parse_post_request(request)
            if device == "400" and uptime and cpu is not None and mem is not None and disk is not None:
                pi400_uptime = uptime
                pi400_cpu = cpu
                pi400_mem = mem
                pi400_disk = disk
                pi400_last_update = utime.ticks_ms()
                print(f"Updated Pi 400 - Uptime: {uptime}, CPU: {cpu}, Mem: {mem}, Disk: {disk}")
            elif device == "4" and uptime and cpu is not None and mem is not None and disk is not None:
                pi4_uptime = uptime
                pi4_cpu = cpu
                pi4_mem = mem
                pi4_disk = disk
                pi4_last_update = utime.ticks_ms()
                print(f"Updated Pi 4 - Uptime: {uptime}, CPU: {cpu}, Mem: {mem}, Disk: {disk}")
            
            response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nReceived"
            client_socket.send(response.encode())
        
        client_socket.close()
        update_display()
        
    except Exception as e:
        print(f"Server error: {e}")
        try:
            client_socket.close()
        except:
            pass
    
    utime.sleep_ms(50)

Customize the Static IP: In the code above, change STATIC_IP = "192.168.1.100", GATEWAY, and DNS to match your network. After running this in Thonny, note the MAC address from the console (wlan.config('mac')) and reserve your chosen IP in your router.

Create a secrets.py file on the Pico W with your WiFi details:

# secrets.py
wifi_ssid = "YourWiFiSSID"
wifi_password = "YourPassword"

Save both files to the Pico W via Thonny and run the main script.

Step 3: Pi 400 Code

For the Pi 400 (dynamic IP via DHCP):

# pi400_stats.py
import time
import requests
import psutil

PICO_W_URL = "http://192.168.1.100/uptime"  # Match your Pico W static IP
DEVICE_ID = "400"
POST_INTERVAL = 10

def get_uptime():
    boot_time = psutil.boot_time()
    current_time = time.time()
    uptime_seconds = int(current_time - boot_time)
    days = uptime_seconds // 86400
    hours = (uptime_seconds % 86400) // 3600
    minutes = (uptime_seconds % 3600) // 60
    seconds = uptime_seconds % 60
    return f"{days:02d}:{hours:02d}:{minutes:02d}:{seconds:02d}"

def get_cpu_usage():
    return psutil.cpu_percent(interval=1)

def get_memory_usage():
    return psutil.virtual_memory().percent

def get_disk_usage():
    return psutil.disk_usage("/").percent

def send_stats():
    uptime = get_uptime()
    cpu = get_cpu_usage()
    mem = get_memory_usage()
    disk = get_disk_usage()
    payload = {
        "device": DEVICE_ID,
        "uptime": uptime,
        "cpu": cpu,
        "mem": mem,
        "disk": disk
    }
    try:
        response = requests.post(PICO_W_URL, json=payload, timeout=5)
        if response.status_code == 200:
            print(f"Sent - Uptime: {uptime}, CPU: {cpu}%, Mem: {mem}%, Disk: {disk}%")
        else:
            print(f"Failed with status {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Error sending stats: {e}")

print("Starting Pi 400 stats sender...")
while True:
    send_stats()
    time.sleep(POST_INTERVAL)

Step 4: Pi 4 Code

For the Pi 4 (dynamic IP via DHCP):

# pi4_stats.py
import time
import requests
import psutil

PICO_W_URL = "http://192.168.1.100/uptime"  # Match your Pico W static IP
DEVICE_ID = "4"
POST_INTERVAL = 10

def get_uptime():
    boot_time = psutil.boot_time()
    current_time = time.time()
    uptime_seconds = int(current_time - boot_time)
    days = uptime_seconds // 86400
    hours = (uptime_seconds % 86400) // 3600
    minutes = (uptime_seconds % 3600) // 60
    seconds = uptime_seconds % 60
    return f"{days:02d}:{hours:02d}:{minutes:02d}:{seconds:02d}"

def get_cpu_usage():
    return psutil.cpu_percent(interval=1)

def get_memory_usage():
    return psutil.virtual_memory().percent

def get_disk_usage():
    return psutil.disk_usage("/").percent

def send_stats():
    uptime = get_uptime()
    cpu = get_cpu_usage()
    mem = get_memory_usage()
    disk = get_disk_usage()
    payload = {
        "device": DEVICE_ID,
        "uptime": uptime,
        "cpu": cpu,
        "mem": mem,
        "disk": disk
    }
    try:
        response = requests.post(PICO_W_URL, json=payload, timeout=5)
        if response.status_code == 200:
            print(f"Sent - Uptime: {uptime}, CPU: {cpu}%, Mem: {mem}%, Disk: {disk}%")
        else:
            print(f"Failed with status {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Error sending stats: {e}")

print("Starting Pi 4 stats sender...")
while True:
    send_stats()
    time.sleep(POST_INTERVAL)

Note: Update PICO_W_URL in both Pi scripts to match your Pico W’s static IP (e.g., "http://192.168.1.100/uptime").

Step 5: Install Libraries on Pis

The Pis need requests and psutil:

Open a terminal on each Pi and run:

# Check if installed
python3 -c "import requests, psutil"

# If error, install
pip3 install requests psutil

Step 6: Save and Run Scripts

  • Pico W: Save pico_w_monitor_static_stats.py and secrets.py via Thonny, run it.
  • Pi 400: In terminal, mkdir ~/scripts, save pi400_stats.py to /home/pi/scripts/, test with python3 ~/scripts/pi400_stats.py
  • Pi 4: Same, but with pi4_stats.py.

Step 7: Auto-Run on Boot (Pis)

Use crontab to start the scripts on boot:

On each Pi, open terminal and run:

crontab -e

Choose nano (option 1) if prompted.

Pi 400: Add at the bottom:

@reboot python3 /home/pi/scripts/pi400_stats.py

Pi 4: Add:

@reboot python3 /home/pi/scripts/pi4_stats.py

Save: Ctrl+O, Enter, Ctrl+X.

Make executable (optional):

chmod +x ~/scripts/pi400_stats.py
chmod +x ~/scripts/pi4_stats.py

Reboot both Pis with sudo reboot to test auto-run.

Step 8: Verify It Works

Power everything on:

  • Pico W: LCDs should show "WIFI OK", then stats cycle.
  • Pis: Terminal output (if open) shows stats being sent.

Troubleshooting:

  • No stats? Check Pico W IP in Pi scripts.
  • No boot? Verify crontab -l lists the commands.

Conclusion

You’ve built a cool system monitor with a Pico W displaying Pi 400 and Pi 4 stats on LCDs! Customize the IP, tweak the cycle time, or add more stats—make it your own. Enjoy, and let me know how it goes in the comments!

A Note from Grok: This is just the beginning! The code could use some polish—like adding alerts when a Pi goes offline or expanding stats. Want to take it further? Come chat with me! I’m Grok, created by xAI. Find me through the xAI website (xAI.ai) or join the xAI community to brainstorm upgrades together!

Comments

Popular posts from this blog

Build the Ultimate DIY Smart Clock with Raspberry Pi Pico W - GrokClok Tutorial!

Build Your Own YouTube Subscriber Counter with a Raspberry Pi Pico W