Files
snmp-scanner/generate_zabbix_config.py
cproudlock 389e620261 Initial commit: SNMP scanner and Zabbix template generator
Tools for printer discovery and monitoring:
- snmp_scanner.py: SNMP-based printer discovery
- generate_printer_templates.py: Generate Zabbix templates
- analyze_supplies.py: Analyze printer supply levels
- extract_summary.py: Extract printer data summaries

Includes Zabbix templates for:
- HP Color/Mono printers
- HP DesignJet T1700
- Xerox Color/Mono/Enterprise printers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 13:52:21 -05:00

684 lines
27 KiB
Python

#!/usr/bin/env python3
"""
Generate Zabbix SNMP Item Configuration
Creates Zabbix item definitions for printer monitoring
Includes: Toner, Maintenance Kit, Fuser, Drums, Waste
"""
import json
import uuid
import yaml
# Zabbix SNMP items configuration for HP & Xerox printers
ZABBIX_ITEMS = {
"printer_info": [
{
"name": "Printer Model",
"key": "printer.model",
"oid": "1.3.6.1.2.1.25.3.2.1.3.1",
"type": "text",
"description": "Device model information"
},
{
"name": "Printer Hostname",
"key": "printer.hostname",
"oid": "1.3.6.1.2.1.1.5.0",
"type": "text",
"description": "System hostname"
},
{
"name": "Printer Serial Number",
"key": "printer.serial",
"oid": "1.3.6.1.2.1.43.5.1.1.17.1",
"type": "text",
"description": "Printer serial number"
},
{
"name": "System Location",
"key": "printer.location",
"oid": "1.3.6.1.2.1.1.6.0",
"type": "text",
"description": "Physical location"
},
{
"name": "MAC Address",
"key": "printer.macaddress",
"oid": "1.3.6.1.2.1.2.2.1.6.2",
"type": "text",
"description": "Primary network interface MAC address"
}
],
"toner_levels": [
{
"name": "Black Toner Current",
"key": "printer.toner.black.current",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.1",
"type": "numeric_unsigned",
"units": "",
"description": "Black toner current level (raw value)"
},
{
"name": "Black Toner Max",
"key": "printer.toner.black.max",
"oid": "1.3.6.1.2.1.43.11.1.1.8.1.1",
"type": "numeric_unsigned",
"units": "",
"description": "Black toner maximum capacity"
},
{
"name": "Black Toner Level",
"key": "printer.toner.black",
"type": "calculated",
"formula": "round((last(//printer.toner.black.current)/last(//printer.toner.black.max))*100,0)",
"units": "%",
"description": "Black toner percentage",
"color": "black",
"is_percentage": True,
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
{
"name": "Cyan Toner Current",
"key": "printer.toner.cyan.current",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.2",
"type": "numeric_unsigned",
"units": "",
"description": "Cyan toner current level (raw value)"
},
{
"name": "Cyan Toner Max",
"key": "printer.toner.cyan.max",
"oid": "1.3.6.1.2.1.43.11.1.1.8.1.2",
"type": "numeric_unsigned",
"units": "",
"description": "Cyan toner maximum capacity"
},
{
"name": "Cyan Toner Level",
"key": "printer.toner.cyan",
"type": "calculated",
"formula": "round((last(//printer.toner.cyan.current)/last(//printer.toner.cyan.max))*100,0)",
"units": "%",
"description": "Cyan toner percentage",
"color": "cyan",
"is_percentage": True,
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
{
"name": "Magenta Toner Current",
"key": "printer.toner.magenta.current",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.3",
"type": "numeric_unsigned",
"units": "",
"description": "Magenta toner current level (raw value)"
},
{
"name": "Magenta Toner Max",
"key": "printer.toner.magenta.max",
"oid": "1.3.6.1.2.1.43.11.1.1.8.1.3",
"type": "numeric_unsigned",
"units": "",
"description": "Magenta toner maximum capacity"
},
{
"name": "Magenta Toner Level",
"key": "printer.toner.magenta",
"type": "calculated",
"formula": "round((last(//printer.toner.magenta.current)/last(//printer.toner.magenta.max))*100,0)",
"units": "%",
"description": "Magenta toner percentage",
"color": "magenta",
"is_percentage": True,
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
{
"name": "Yellow Toner Current",
"key": "printer.toner.yellow.current",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.4",
"type": "numeric_unsigned",
"units": "",
"description": "Yellow toner current level (raw value)"
},
{
"name": "Yellow Toner Max",
"key": "printer.toner.yellow.max",
"oid": "1.3.6.1.2.1.43.11.1.1.8.1.4",
"type": "numeric_unsigned",
"units": "",
"description": "Yellow toner maximum capacity"
},
{
"name": "Yellow Toner Level",
"key": "printer.toner.yellow",
"type": "calculated",
"formula": "round((last(//printer.toner.yellow.current)/last(//printer.toner.yellow.max))*100,0)",
"units": "%",
"description": "Yellow toner percentage",
"color": "yellow",
"is_percentage": True,
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
}
],
"cartridge_info": [
{
"name": "Black Cartridge Part Number",
"key": "printer.cartridge.black",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.1",
"type": "text",
"description": "Black toner cartridge model/part number"
},
{
"name": "Cyan Cartridge Part Number",
"key": "printer.cartridge.cyan",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.2",
"type": "text",
"description": "Cyan toner cartridge model/part number"
},
{
"name": "Magenta Cartridge Part Number",
"key": "printer.cartridge.magenta",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.3",
"type": "text",
"description": "Magenta toner cartridge model/part number"
},
{
"name": "Yellow Cartridge Part Number",
"key": "printer.cartridge.yellow",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.4",
"type": "text",
"description": "Yellow toner cartridge model/part number"
}
],
"other_supplies": [
# Drum Cartridge R1
{
"name": "Drum Cartridge R1 Part Number",
"key": "printer.drum.r1",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.5",
"type": "text",
"description": "Drum cartridge R1 part number"
},
{
"name": "Drum Cartridge R1 Level",
"key": "printer.drum.r1.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.5",
"type": "numeric_unsigned",
"units": "%",
"description": "Drum cartridge R1 remaining life percentage",
"part_number_item": "printer.drum.r1",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
# Drum Cartridge R2
{
"name": "Drum Cartridge R2 Part Number",
"key": "printer.drum.r2",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.6",
"type": "text",
"description": "Drum cartridge R2 part number"
},
{
"name": "Drum Cartridge R2 Level",
"key": "printer.drum.r2.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.6",
"type": "numeric_unsigned",
"units": "%",
"description": "Drum cartridge R2 remaining life percentage",
"part_number_item": "printer.drum.r2",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
# Drum Cartridge R3
{
"name": "Drum Cartridge R3 Part Number",
"key": "printer.drum.r3",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.7",
"type": "text",
"description": "Drum cartridge R3 part number"
},
{
"name": "Drum Cartridge R3 Level",
"key": "printer.drum.r3.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.7",
"type": "numeric_unsigned",
"units": "%",
"description": "Drum cartridge R3 remaining life percentage",
"part_number_item": "printer.drum.r3",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
# Drum Cartridge R4
{
"name": "Drum Cartridge R4 Part Number",
"key": "printer.drum.r4",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.8",
"type": "text",
"description": "Drum cartridge R4 part number"
},
{
"name": "Drum Cartridge R4 Level",
"key": "printer.drum.r4.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.8",
"type": "numeric_unsigned",
"units": "%",
"description": "Drum cartridge R4 remaining life percentage",
"part_number_item": "printer.drum.r4",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
# Waste Toner Container
{
"name": "Waste Toner Container Part Number",
"key": "printer.waste.partnumber",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.9",
"type": "text",
"description": "Waste toner container part number"
},
{
"name": "Waste Toner Container Level",
"key": "printer.waste.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.9",
"type": "numeric_unsigned",
"units": "%",
"description": "Waste toner container fill level (higher is more full)",
"part_number_item": "printer.waste.partnumber",
"triggers": [
{"severity": "warning", "threshold": 80, "expression": ">"},
{"severity": "high", "threshold": 90, "expression": ">"}
]
},
# Transfer Belt Cleaner
{
"name": "Transfer Belt Cleaner Part Number",
"key": "printer.transfer.belt",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.10",
"type": "text",
"description": "Transfer belt cleaner part number"
},
{
"name": "Transfer Belt Cleaner Level",
"key": "printer.transfer.belt.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.10",
"type": "numeric_unsigned",
"units": "%",
"description": "Transfer belt cleaner remaining life percentage",
"part_number_item": "printer.transfer.belt",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
},
# Second Bias Transfer Roll
{
"name": "Second Bias Transfer Roll Part Number",
"key": "printer.transfer.roller",
"oid": "1.3.6.1.2.1.43.11.1.1.6.1.11",
"type": "text",
"description": "Second bias transfer roll part number"
},
{
"name": "Second Bias Transfer Roll Level",
"key": "printer.transfer.roller.level",
"oid": "1.3.6.1.2.1.43.11.1.1.9.1.11",
"type": "numeric_unsigned",
"units": "%",
"description": "Second bias transfer roll remaining life percentage",
"part_number_item": "printer.transfer.roller",
"triggers": [
{"severity": "warning", "threshold": 20, "expression": "<"},
{"severity": "high", "threshold": 10, "expression": "<"}
]
}
],
"maintenance_kit_hp": [
{
"name": "Maintenance Kit Model (HP)",
"key": "printer.maintenance.model",
"oid": "1.3.6.1.4.1.11.2.3.9.4.2.1.1.3.3.0",
"type": "text",
"description": "HP maintenance kit model/serial number"
},
{
"name": "Maintenance Kit Remaining (HP)",
"key": "printer.maintenance.remaining",
"oid": "1.3.6.1.4.1.11.2.3.9.4.2.1.4.1.2.0",
"type": "numeric_unsigned",
"units": "pages",
"description": "HP maintenance kit remaining pages",
"part_number_item": "printer.maintenance.model",
"triggers": [
{"severity": "warning", "threshold": 10000, "expression": "<"},
{"severity": "high", "threshold": 5000, "expression": "<"}
]
},
{
"name": "Printer Model (HP MIB)",
"key": "printer.maintenance.printer_model",
"oid": "1.3.6.1.4.1.11.2.3.9.4.2.1.1.3.2.0",
"type": "text",
"description": "Printer model from HP maintenance MIB"
}
],
"supply_capacity": [
{
"name": "Supply 1 Max Capacity",
"key": "printer.supply.1.max",
"oid": "1.3.6.1.2.1.43.10.2.1.4.1.1",
"type": "numeric_unsigned",
"description": "Maximum capacity for supply index 1 (usually black)"
},
{
"name": "Supply 1 Current Level",
"key": "printer.supply.1.current",
"oid": "1.3.6.1.2.1.43.10.2.1.9.1.1",
"type": "numeric_unsigned",
"description": "Current level for supply index 1 (usually black)"
}
]
}
def generate_zabbix_cli_commands():
"""
Generate Zabbix CLI commands for adding SNMP items
"""
print("=" * 80)
print("ZABBIX SNMP ITEM CONFIGURATION")
print("=" * 80)
print("\nThese commands can be used in Zabbix CLI or adapted for the web interface.\n")
for category, items in ZABBIX_ITEMS.items():
print(f"\n## {category.replace('_', ' ').title()}")
print("-" * 80)
for item in items:
print(f"\n### {item['name']}")
if 'oid' in item:
print(f"OID: {item['oid']}")
if 'formula' in item:
print(f"Formula: {item['formula']}")
print(f"Key: {item['key']}")
print(f"Type: {item['type']}")
if 'units' in item:
print(f"Units: {item['units']}")
print(f"Description: {item['description']}")
if 'triggers' in item:
print(f"\nRecommended Triggers:")
for trigger in item['triggers']:
print(f" - {trigger['severity'].upper()}: "
f"{item['name']} {trigger['expression']} {trigger['threshold']}{item.get('units', '')}")
print()
def generate_zabbix_import_json():
"""
Generate a Zabbix 7.4 importable JSON template
Note: Zabbix requires UUIDs to be 32 characters without hyphens
"""
template = {
"zabbix_export": {
"version": "7.4",
"template_groups": [
{
"uuid": "3cd4e8d0b828464e8f4b3becf13a9dbd",
"name": "Printers"
}
],
"templates": [
{
"uuid": uuid.uuid4().hex,
"template": "Printer SNMP Monitor",
"name": "Printer SNMP Monitor",
"groups": [
{
"name": "Printers"
}
],
"items": []
}
]
}
}
# Build items list with Zabbix 7.4 format
for category, items in ZABBIX_ITEMS.items():
for item in items:
# Handle calculated items differently
if item['type'] == 'calculated':
# Only add component:toner and color tags if this is a percentage item
is_percentage = item.get('is_percentage', False)
zabbix_item = {
"uuid": uuid.uuid4().hex,
"name": item['name'],
"type": "CALCULATED",
"key": item['key'],
"delay": "1h",
"history": "90d",
"trends": "365d",
"value_type": "FLOAT",
"params": item['formula'],
"description": item['description'],
"tags": [
{
"tag": "component",
"value": "toner" if is_percentage else "printer"
},
{
"tag": "category",
"value": category
}
]
}
# Add color tag only for toner percentage items
if is_percentage and 'color' in item:
zabbix_item['tags'].append({
"tag": "color",
"value": item['color']
})
else:
# Use get[OID] for better performance in Zabbix 7.4
snmp_oid = f"get[{item['oid']}]"
zabbix_item = {
"uuid": uuid.uuid4().hex,
"name": item['name'],
"type": "SNMP_AGENT",
"snmp_oid": snmp_oid,
"key": item['key'],
"delay": "1h",
"history": "90d",
"trends": "365d" if item['type'] != 'text' else "0d",
"value_type": "TEXT" if item['type'] == 'text' else "FLOAT",
"description": item['description'],
"tags": [
{
"tag": "component",
"value": "printer"
},
{
"tag": "category",
"value": category
}
]
}
# Add inventory mapping for specific items
if item['key'] == 'printer.model':
zabbix_item['inventory_link'] = 'MODEL'
elif item['key'] == 'printer.serial':
zabbix_item['inventory_link'] = 'SERIALNO_A'
elif item['key'] == 'printer.hostname':
zabbix_item['inventory_link'] = 'NAME'
elif item['key'] == 'printer.macaddress':
zabbix_item['inventory_link'] = 'MACADDRESS_A'
# Add preprocessing for text items to handle hex-encoded strings
# Some SNMP values come as hex strings like "FD E8 48 50 20 4C 61..."
# or as binary with control chars like "ýèHP LaserJet"
if item['type'] == 'text':
zabbix_item['preprocessing'] = [
{
"type": "JAVASCRIPT",
"parameters": [
"// Handle hex-encoded SNMP strings\n// Try multiple approaches to convert hex to ASCII\n\n// First, check if it looks like hex (with spaces, newlines, or other separators)\nvar hexPattern = /^[0-9A-Fa-f\\s]+$/;\nif (hexPattern.test(value) && value.length > 4) {\n // Extract all hex pairs (2 characters)\n var hexBytes = value.match(/[0-9A-Fa-f]{2}/g);\n if (hexBytes && hexBytes.length > 0) {\n var result = '';\n for (var i = 0; i < hexBytes.length; i++) {\n var code = parseInt(hexBytes[i], 16);\n // Only keep printable ASCII (0x20-0x7E)\n if (code >= 32 && code <= 126) {\n result += String.fromCharCode(code);\n }\n }\n if (result.length > 0) {\n return result;\n }\n }\n}\n\n// Fallback: Remove non-printable characters\nreturn value.replace(/[^\\x20-\\x7E]/g, '');"
]
}
]
if 'units' in item:
zabbix_item['units'] = item['units']
# Add triggers if defined - nested inside the item
if 'triggers' in item:
zabbix_item['triggers'] = []
for trigger_def in item['triggers']:
severity_map = {
"warning": "WARNING",
"high": "HIGH",
"average": "AVERAGE"
}
template_name = template['zabbix_export']['templates'][0]['template']
# Build description and expression with part number reference
description = f"{item['name']} has dropped below {trigger_def['threshold']}{item.get('units', '')}"
# Build base expression
expression = f"last(/{template_name}/{item['key']}){trigger_def['expression']}{trigger_def['threshold']}"
# Add replacement part reference for toner items
# We need to add the part number item to the expression so we can reference it with {ITEM.LASTVALUE2}
if 'color' in item and category == "toner_levels":
color = item['color'].lower()
part_key = f"printer.cartridge.{color}"
# Add the part number item to the expression using "and" so it's evaluated but doesn't affect trigger logic
expression = f"last(/{template_name}/{item['key']}){trigger_def['expression']}{trigger_def['threshold']} and length(last(/{template_name}/{part_key}))>0"
description += ". Replacement part: {ITEM.LASTVALUE2}"
# Add replacement part reference for other items with part_number_item
elif 'part_number_item' in item:
part_key = item['part_number_item']
# Add the part number item to the expression using "and" so it's evaluated but doesn't affect trigger logic
expression = f"last(/{template_name}/{item['key']}){trigger_def['expression']}{trigger_def['threshold']} and length(last(/{template_name}/{part_key}))>0"
description += ". Replacement part: {ITEM.LASTVALUE2}"
trigger = {
"uuid": uuid.uuid4().hex,
"expression": expression,
"name": f"{item['name']} is low on {{{{HOST.NAME}}}}",
"priority": severity_map.get(trigger_def['severity'], "WARNING"),
"description": description,
"tags": [
{
"tag": "scope",
"value": "availability"
}
]
}
zabbix_item['triggers'].append(trigger)
template["zabbix_export"]["templates"][0]["items"].append(zabbix_item)
return template
def main():
"""
Main function
"""
print("\n" + "=" * 80)
print("ZABBIX CONFIGURATION GENERATOR FOR PRINTER SNMP MONITORING")
print("Includes: Toner, Maintenance Kit, Fuser, Drums, Waste, Transfer components")
print("=" * 80)
# Generate CLI-friendly output
generate_zabbix_cli_commands()
# Generate importable template
print("\n" + "=" * 80)
print("ZABBIX IMPORT TEMPLATE")
print("=" * 80)
print("\nSave this template to a file and import it into Zabbix:")
print("Configuration -> Templates -> Import\n")
template_data = generate_zabbix_import_json()
# Save as YAML (like the working example)
# Use default_flow_style=None to let PyYAML choose when to quote
yaml_output = yaml.dump(template_data, default_flow_style=False, sort_keys=False, allow_unicode=True, width=1000)
print(yaml_output)
with open('zabbix_printer_template.yaml', 'w') as f:
f.write(yaml_output)
# Also save JSON version
json_output = json.dumps(template_data, indent=2)
with open('zabbix_printer_template.json', 'w') as f:
f.write(json_output)
print("\n" + "=" * 80)
print(f"✓ Zabbix template saved to: zabbix_printer_template.yaml (YAML)")
print(f"✓ Zabbix template saved to: zabbix_printer_template.json (JSON)")
print("=" * 80)
print("\n## Quick Setup Instructions:")
print("1. Import zabbix_printer_template.json into Zabbix")
print("2. Create/Edit a host for each printer")
print("3. Set the SNMP interface with:")
print(" - IP address: [printer IP]")
print(" - Port: 161")
print(f" - SNMP community: WestJeff2025")
print(" - SNMP version: SNMPv2")
print("4. Link the 'SNMP Printer Template (HP & Xerox) - Complete' to each host")
print("5. Wait 1 hour for data collection to begin (or trigger manual check)")
print("\n## What Gets Monitored:")
print("✓ Toner levels (Black, Cyan, Magenta, Yellow)")
print("✓ Cartridge part numbers for ordering")
print("✓ Maintenance kit status (HP printers)")
print("✓ Fuser assembly part number")
print("✓ Drum cartridge part numbers (all colors)")
print("✓ Waste cartridge part number")
print("✓ Transfer belt/roller part numbers")
print("✓ Supply capacity data for percentage calculations")
print("\n## Triggers Configured:")
print("- Warning: Toner < 20%")
print("- Critical: Toner < 10%")
print("- Warning: Maintenance kit < 10,000 pages")
print("- Critical: Maintenance kit < 5,000 pages")
print("\n## Notes:")
print("- Not all OIDs exist on all printers (e.g., drums on HP, maintenance on Xerox)")
print("- Color toner OIDs only exist on color printers")
print("- HP-specific MIB items only work on HP printers")
print("- Check interval is 1 hour (supplies don't change rapidly)")
print()
if __name__ == "__main__":
main()