// Project Writeup

Printer Status Monitor

A Python script that polls every printer on the plant floor via SNMP, collects supply levels and status codes, and generates a self-contained HTML dashboard — one command, no agents, no cloud.

Python SNMP Hardware Internal Tool

The Problem

The Liberty Ball Plant has 16 printers spread across two buildings — a mix of Zebra ZD621d thermal label printers, HP LaserJets, and Ricoh MFPs. When a printer goes down on the shipping line or a label printer runs out of ribbon mid-shift, the first person to notice is usually the operator standing in front of it. By then it's already causing a bottleneck.

There was no single view of which printers were online, which had low supplies, or which were throwing errors. The HP and Ricoh web consoles each have their own status pages, but nobody is going to check 16 browser tabs. Zebra printers don't even have a useful web UI — their status lives entirely in SNMP.

The goal was a single command that scans every printer and produces one HTML report you can open, print, or pin to a signage board.

Architecture

The system is two files: a Python script (printer_monitor.py) and a JSON config (printers.json). The script reads the config, polls all printers in parallel via asyncio.gather(), and writes a fully self-contained HTML dashboard to disk.

printers.json                     printer_monitor.py
┌─────────────────────┐           ┌──────────────────────────────────┐
│ 16 entries:         │──────────▶│ For each printer (in parallel):  │
│   name, ip, type,   │           │   1. TCP reachability check      │
│   model, location,  │           │      (9100 → 80 → SNMP fallback)│
│   building          │           │   2. SNMP GET: sysDescr, status, │
└─────────────────────┘           │      uptime, display message     │
                                  │   3. SNMP WALK: supply table     │
                                  │      (desc, max, level per row)  │
                                  │   4. Brand-specific extras:      │
                                  │      Zebra → label count,        │
                                  │              head usage (inches)  │
                                  │      HP/Ricoh → page count       │
                                  └──────────┬───────────────────────┘
                                             │
                                             ▼
                                  ┌──────────────────────────────────┐
                                  │ printer_status.html              │
                                  │   KPIs: Online │ Offline │       │
                                  │         Warnings │ Low Supply    │
                                  │   Cards grouped by building,     │
                                  │   collapsible sections,          │
                                  │   supply bars, status badges     │
                                  └──────────────────────────────────┘

Dashboard Output

Printer Status Monitor dashboard showing 16 printers across two buildings with KPI tiles, supply level bars, and building-grouped card layout

Live scan of 16 printers across Buildings 565 and 740. Cards show status badges, supply level bars, page counts, and uptime. Sensitive details blurred.

Key Decisions

  • Three-tier reachability check
    Not every printer responds on the same port. Zebras answer on TCP 9100 (RAW print). HPs answer on 80 (HTTP). Some devices only respond on UDP 161 (SNMP). The monitor tries TCP 9100 first, then TCP 80, then falls back to an actual SNMP GET on sysDescr. If all three fail, the printer is marked offline. This avoids false negatives from devices that are up but only speak one protocol.
  • Parallel polling with asyncio
    With 16 printers and a 2-second SNMP timeout, sequential polling would take 30+ seconds in the worst case. Using asyncio.gather() to poll all printers simultaneously brings the total scan time down to roughly the timeout of the slowest device — usually under 5 seconds for the full fleet.
  • SNMP special values for supply levels
    The Printer MIB uses magic numbers for supply levels: -1 means "some remaining but unknown amount," -2 means "unknown," and -3 means "empty or at max capacity." These get misread as negative percentages if you divide blindly. Each value maps to its own display treatment — "Some remaining" text, a gray unknown bar, or a red empty indicator.
  • Brand-specific OID branches
    Standard Printer MIB OIDs (RFC 3805) cover status, supplies, and page counts across all brands. But Zebra printers expose label count and printhead usage in inches under their private enterprise OID tree (1.3.6.1.4.1.10642). The monitor checks the printer type from the config and queries the relevant extras — useful for predicting when a Zebra printhead is approaching replacement.
  • pysnmp v4/v6 compatibility shim
    The pysnmp-lextudio fork moved the high-level API from synchronous (pysnmp.hlapi) to asyncio-native (pysnmp.hlapi.asyncio) between v4 and v6. The import block tries the v6 path first, then falls back to v4, so the script works on either version without changes.
  • Building-grouped output
    The HTML dashboard groups printer cards by building (565 and 740), with collapsible sections. This mirrors how the plant staff thinks about the printers — "is anything down in 740?" is a more natural question than scrolling a flat list of 16 cards.

Lessons Learned

SNMP WALK results vary by firmware

Different printer firmwares return supply table rows in different orders, and some omit rows entirely. The Ricoh SP 377 returns a single supply entry (toner). The HP LaserJets return four (CMYK or just black + drum + fuser + transfer). The Zebras return ribbon and sometimes nothing at all if no ribbon is loaded. The code can't assume a fixed row count or a consistent ordering — it walks the full table and correlates by index position.

Hex-encoded strings are common

Many printers return SNMP string values as hex-encoded byte sequences (prefixed with 0x) rather than plain ASCII. The prtConsoleDisplayBufferText OID — the printer's front panel message — is a frequent offender. Every string value gets checked for the 0x prefix and decoded to UTF-8 if found, with null bytes stripped.

Static HTML is the right output format

The initial instinct was to build a live-polling web app, but a static HTML file turned out to be better for this use case. It can be opened by anyone without a running server, emailed as an attachment, pinned to a digital signage board, or printed and taped to the wall. Scheduling the script in Task Scheduler produces automatic refreshes without the complexity of a persistent daemon.

Stack

Language
Python 3 (asyncio)
Protocol
SNMPv2c (RFC 3805)
Library
pysnmp-lextudio
Config
JSON (printers.json)
Output
Self-contained HTML
Design
DM Sans + JetBrains Mono, GitHub-dark palette

Status & Outcomes

The monitor currently tracks 16 printers across Buildings 565 and 740 — ten Zebra ZD621d label printers (shipping, packing, repair), three HP LaserJets, two Ricoh units, and one HP MFP. The fleet breaks down as 10 Zebra thermal, 4 HP, and 2 Ricoh.

Running it once in the morning catches overnight failures before the first shift hits the floor. Supply-level tracking has cut down on surprise ribbon-outs on the Zebra printers, which previously caused shipping line stoppages while someone tracked down a replacement roll.

The JSON config makes it easy to add or remove printers as the fleet changes — no code edits required, just a new entry in the array. The same script and config format could be dropped on any site with SNMP-enabled printers.