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
Post a Comment