Difficulty: Intermediate | Time: 2 Hours | Cost: ~$115 USD

Mapping a local subnet manually is a tedious process of pinging IP ranges and checking ARP tables. By turning a Raspberry Pi into a dedicated network edge scanner, you can automate the discovery of local devices and generate a live raspberry pi network diagram that exports to a visual graph while displaying real-time node stats on a physical OLED screen. This project uses Layer 2 ARP scanning to bypass host-level ICMP firewalls, ensuring you see every active device on your LAN, from smart bulbs to hidden IoT hubs.

Project Overview & Hardware Requirements

This build targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS (Bookworm, 64-bit). The Pi 5's PCIe-exposed architecture and upgraded Gigabit Ethernet controller make it ideal for high-speed subnet sweeping without bottlenecking the I2C bus used for the display. We will also use a USB 2.5G Ethernet adapter to create a dual-homed setup, allowing the Pi to scan a specific isolated LAN segment while remaining connected to your main WAN.

Pro Tip: Do not use the Raspberry Pi 4 for this specific build if you plan to scan large subnets (e.g., /16). The Pi 4's USB 3.0 bus shares bandwidth with the Ethernet controller, which can cause packet drops during high-throughput ARP sweeps. The Pi 5's dedicated Ethernet MAC solves this.

Parts List:

  • Compute: Raspberry Pi 5 (8GB) - ~$80
  • Display: 128x64 I2C OLED (SSD1306 driver, 3.3V logic) - ~$12
  • Network: Realtek RTL8156 USB 2.5G Ethernet Adapter (for isolated LAN port) - ~$18
  • Indicators: 2x 5mm LEDs (1 Green, 1 Red), 2x 330Ω resistors - ~$2
  • Consumables: Female-to-female Dupont jumper wires, solderless breadboard or Pi GPIO hammer header.

Network Interface & GPIO Pin Mapping

Before wiring, it is critical to map the physical pins to the BCM GPIO numbers. The Raspberry Pi 5 maintains the standard 40-pin header layout, but I2C bus speeds default to 100kHz. We will configure it for 400kHz later to ensure the OLED refreshes fast enough to draw the network diagram nodes without flickering.

Component Pi 5 Pin (Physical) BCM GPIO Function Wiring Notes & Constraints
OLED VCC 1 3.3V Power Logic Power Do NOT connect to Pin 2 (5V) unless your OLED has an onboard regulator.
OLED GND 6 GND Common Ground Must share ground with the Pi and LED circuit.
OLED SDA 3 GPIO 2 I2C Data Pi 5 has onboard 1.8kΩ pull-ups; no external resistors needed.
OLED SCL 5 GPIO 3 I2C Clock Keep wire length under 10cm to prevent capacitance issues at 400kHz.
WAN LED (+) 11 GPIO 17 Status High Connect anode to GPIO 17 via a 330Ω current-limiting resistor.
LAN LED (+) 13 GPIO 27 Status High Connect anode to GPIO 27 via a 330Ω current-limiting resistor.
LEDs (-) 9 GND Cathode Ground Common ground for both LED cathodes.

Wiring the OLED and Status LEDs

Follow these steps to assemble the hardware. Precision here prevents I2C bus lockups and blown GPIO pins.

  1. De-energize the system: Unplug the Raspberry Pi 5 power supply completely. Never wire GPIO pins while the board is live; a slipped jumper on the 5V rail will instantly destroy the Pi 5's power management IC (PMIC).
  2. Connect the I2C Display: Using female-to-female jumpers, connect the SSD1306 OLED VCC to Physical Pin 1 (3.3V), GND to Pin 6, SDA to Pin 3, and SCL to Pin 5.
  3. Wire the Status LEDs: Insert the 330Ω resistors into the breadboard. Connect GPIO 17 (Pin 11) through a resistor to the Green LED anode. Connect GPIO 27 (Pin 13) through a resistor to the Red LED anode. Tie both cathodes to Physical Pin 9 (GND).
  4. Attach the USB NIC: Plug the Realtek RTL8156 USB 2.5G adapter into one of the blue USB 3.0 ports on the Pi 5. Connect an Ethernet cable from your isolated target LAN to this adapter.
  5. Enable I2C and set bus speed: Boot the Pi, open a terminal, and run sudo raspi-config. Navigate to Interface Options > I2C and enable it. Next, edit the boot config to increase I2C speed: sudo nano /boot/firmware/config.txt and add the line dtparam=i2c_arm_baudrate=400000. Reboot.

Python Network Scanner & Diagram Code

This script targets the Raspberry Pi 5 (Bookworm OS). It uses scapy for Layer 2 ARP scanning and luma.oled for the display. It also generates a Graphviz .dot file, which is the industry standard for rendering programmatic network diagrams.

Prerequisites: Install the required libraries via terminal:
sudo apt update && sudo apt install python3-pip graphviz -y
pip3 install scapy luma.oled luma.core pillow gpiod --break-system-packages

#!/usr/bin/env python3
# network_diagram_scanner.py
# Target: Raspberry Pi 5 (Bookworm 64-bit)

import time
import socket
import subprocess
import scapy.all as scapy
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
import RPi.GPIO as GPIO

# --- PIN DEFINITIONS ---
WAN_LED_PIN = 17  # BCM 17 (Physical 11)
LAN_LED_PIN = 27  # BCM 27 (Physical 13)

# --- I2C SETUP ---
try:
    serial = i2c(port=1, address=0x3C)
    oled = ssd1306(serial, width=128, height=64)
except Exception as e:
    print(f'FATAL: OLED initialization failed. Check I2C wiring. Error: {e}')
    exit(1)

# --- GPIO SETUP ---
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(WAN_LED_PIN, GPIO.OUT)
GPIO.setup(LAN_LED_PIN, GPIO.OUT)

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    return ip

def scan_network(ip_range):
    GPIO.output(LAN_LED_PIN, GPIO.HIGH)
    arp_request = scapy.ARP(pdst=ip_range)
    broadcast = scapy.Ether(dst='ff:ff:ff:ff:ff:ff')
    arp_broadcast = broadcast / arp_request
    
    # Timeout set to 2s to prevent hanging on unresponsive segments
    answered_list = scapy.srp(arp_broadcast, timeout=2, verbose=False)[0]
    
    devices = []
    for element in answered_list:
        devices.append({'ip': element[1].psrc, 'mac': element[1].hwsrc})
    
    GPIO.output(LAN_LED_PIN, GPIO.LOW)
    return devices

def generate_dot_diagram(local_ip, devices):
    dot_content = 'digraph NetworkTopology {\n'
    dot_content += '  node [shape=box, style=filled, fillcolor=lightblue];\n'
    dot_content += f'  "Pi5_Router" [fillcolor=gold, label="Pi5\n{local_ip}"];\n'
    
    for dev in devices:
        if dev['ip'] != local_ip:
            dot_content += f'  "Pi5_Router" -> "{dev["ip"]}" [label="{dev["mac"]}"];\n'
    
    dot_content += '}\n'
    
    with open('/home/pi/network_topology.dot', 'w') as f:
        f.write(dot_content)
    
    # Render to PNG using Graphviz
    subprocess.run(['dot', '-Tpng', '/home/pi/network_topology.dot', '-o', '/home/pi/network_diagram.png'])

def draw_oled_ui(local_ip, device_count):
    font = ImageFont.load_default()
    with canvas(oled) as draw:
        draw.text((0, 0), text='NET SCANNER v1.2', font=font, fill='white')
        draw.text((0, 15), text=f'Host: {local_ip}', font=font, fill='white')
        draw.text((0, 30), text=f'Nodes Found: {device_count}', font=font, fill='white')
        draw.rectangle((0, 50, 128, 64), outline='white')
        draw.text((5, 52), text='Diagram Exported OK', font=font, fill='white')

if __name__ == '__main__':
    try:
        local_ip = get_local_ip()
        subnet = f'{".".join(local_ip.split(".")[:3])}.0/24'
        
        GPIO.output(WAN_LED_PIN, GPIO.HIGH)
        print(f'Scanning subnet: {subnet}')
        
        devices = scan_network(subnet)
        print(f'Discovered {len(devices)} devices.')
        
        generate_dot_diagram(local_ip, devices)
        draw_oled_ui(local_ip, len(devices))
        
        print('Network diagram saved to /home/pi/network_diagram.png')
        time.sleep(10) # Keep OLED on for 10s
        
    except PermissionError:
        print('ERROR: Raw sockets require root. Run with sudo.')
    except KeyboardInterrupt:
        print('Scan aborted by user.')
    finally:
        GPIO.cleanup()
        oled.cleanup()

Execute the script with root privileges (required for Scapy's raw socket manipulation):
sudo python3 network_diagram_scanner.py

Debugging: Exact Error Strings & First Checks

When working with raw network sockets and I2C hardware on the Pi 5, you will inevitably hit a few snags. If your script fails, here is the diagnostic decision tree.

The First Three Things to Check

  1. Verify I2C Address: Run sudo i2cdetect -y 1. You should see 3c in the grid. If the grid is empty, your SDA/SCL wires are swapped or the OLED is dead.
  2. Check Interface Names: Run ip a. Ensure your USB Ethernet adapter is recognized (usually eth1 or enx...). If it's missing, the RTL8156 driver isn't loaded. Run sudo apt install r8152-dkms.
  3. Confirm Root Execution: Scapy crafts custom Ethernet frames. The Linux kernel blocks non-root users from doing this. You must use sudo.

Ranked Causes for Common Error Strings

Error 1: PermissionError: [Errno 1] Operation not permitted

  • Cause A (Most Likely): You ran the script without sudo. Scapy requires CAP_NET_RAW capabilities.
  • Cause B: AppArmor or SELinux is restricting Python's network access (rare on default Raspberry Pi OS, but possible on custom hardened images).

Error 2: luma.core.error.DeviceNotFoundError: I2C device not found on address 0x3C

  • Cause A: The OLED uses address 0x3D instead of 0x3C (check the silkscreen on the back of the PCB). Change address=0x3C to 0x3D in the code.
  • Cause B: I2C is disabled in raspi-config.
  • Cause C: The Pi 5's I2C bus is locked up due to a previous crash. Fix by running sudo rmmod i2c_bcm2835 && sudo modprobe i2c_bcm2835.

Error 3: OSError: [Errno 101] Network is unreachable

  • Cause A: The Pi doesn't have an active IP address on the interface Scapy is trying to bind to. This happens if the Ethernet cable is unplugged or the DHCP lease expired.
  • Cause B: You are trying to scan a subnet that doesn't match your current interface's IP range. Scapy will refuse to send ARP requests out of an interface that isn't routed for that subnet.

Extending and Simplifying the Build

Depending on your deployment environment, you may want to scale this project up into a permanent network monitor or strip it down for a quick one-off audit.

How to Simplify (The Headless Audit)

If you are doing a temporary site survey and don't want to wire the OLED or LEDs, delete the luma and RPi.GPIO imports and their associated function calls. Rely entirely on the Graphviz .dot export. You can pull the generated network_diagram.png off the Pi via SCP (scp pi@ip:/home/pi/network_diagram.png ./) and view it on your laptop. This reduces the hardware requirement to just the Pi and the USB NIC.

How to Extend (Continuous MQTT Monitoring)

To turn this from a manual scanner into an active topology monitor, integrate the paho-mqtt library. Wrap the scan_network() function in a while True loop with a 60-second sleep interval. Publish the discovered MAC addresses and IP pairs to an MQTT broker (like Mosquitto or Eclipse). From there, you can ingest the data into Home Assistant or Grafana to build a real-time, interactive web-based raspberry pi network diagram that alerts you via Telegram or Discord when an unauthorized MAC address joins the subnet.

For deeper packet analysis, refer to the official Scapy documentation to implement DHCP sniffing, which will allow your Pi to capture device hostnames as they request IP addresses, adding a layer of human-readable identification to your topology map.