testsAndMisc/python_pkg/brother_printer/data_classes.py
Krzysztof kuhy Rudnicki c4d5631054
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
fix(brother_printer): truthful probe note, pyusb auto-install, stuck-job detection
The CUPS-fallback note blamed a missing pyusb when the probe was really
skipped because CUPS owned the printer. It now names the real reason.
run.sh installs python-pyusb for the system python3 that sudo runs.

A paper misfeed blocks the usb:// backend while CUPS keeps saying
"processing", and cancel leaves that backend hung so the queue never
recovers. The report now flags a job whose page counter is frozen for
15 s, and a backend of a cancelled job, with a y/N offer to SIGKILL it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaBRWtXXWCs3K3yPHSruUC
2026-10-12 16:23:48 +02:00

144 lines
3.8 KiB
Python

"""Data classes for Brother printer status information."""
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class CUPSJob:
"""A single CUPS print job."""
job_id: str
user: str
size: str
date: str
@dataclass
class CUPSQueueStatus:
"""Status of the CUPS print queue for a printer."""
printer_name: str = ""
enabled: bool = True
reason: str = ""
jobs: list[CUPSJob] = field(default_factory=list)
has_backend_errors: bool = False
last_backend_error: str = ""
@dataclass
class PageCountEstimate:
"""Estimated consumable life based on the printer's lifetime page count."""
total_pages: int = 0
toner_pages: int = 0
drum_pages: int = 0
toner_pct_remaining: int = 100
drum_pct_remaining: int = 100
toner_exhausted: bool = False
toner_low: bool = False
drum_near_end: bool = False
# True when total_pages came from the CUPS page log instead of the
# printer's own counter. The log only sees jobs CUPS itself printed, so
# it undercounts and the percentages read optimistically high.
approximate: bool = False
@dataclass
class PageDeliveryCheck:
"""Pages CUPS claims it sent versus pages the printer's counter recorded.
CUPS reports a job as successful once the data leaves the machine, so a page
the printer silently drops still shows up as "printed". The HL-1110 does
exactly that when a page's 600 dpi raster overflows its ~1 MB of memory: it
discards the page, stays READY, and reports no error anywhere. Comparing the
two counters is the only way to notice.
"""
cups_pages: int = 0
printer_pages: int = 0
dropped: int = 0
suspected: bool = False
@dataclass
class USBPortStatus:
"""IEEE 1284 USB printer port status bits."""
paper_empty: bool = False
online: bool = True
error: bool = False
raw_byte: int = 0
@dataclass
class CupsDiagnosis:
"""What the CUPS fallback learned beyond the status code itself."""
# Why port_status is None, in words for the report. Empty when it was read.
probe_note: str = ""
# PID of a usb:// backend still running for a cancelled job; 0 if none.
orphaned_backend_pid: int = 0
@dataclass
class USBResult:
"""Result from a USB PJL query."""
connection: str = "usb"
device: str = ""
product: str = "Brother Laser Printer"
serial: str = ""
status_code: str = ""
display: str = ""
online: str = ""
economode: str = ""
error: str = ""
# Lifetime page count from @PJL INFO PAGECOUNT: the printer's own counter,
# and the authoritative one. Empty when the printer could not be asked
# (CUPS fallback mode), which forces the estimate onto the CUPS page log.
page_count: str = ""
port_status: USBPortStatus | None = None
cups: CupsDiagnosis = field(default_factory=CupsDiagnosis)
@dataclass
class SupplyReadings:
"""Parallel SNMP supply tables (descriptions, capacities, current levels).
The three lists are always populated and indexed together, so they travel
as one object rather than three loose fields on NetworkResult.
"""
descriptions: list[str] = field(default_factory=list)
max_values: list[str] = field(default_factory=list)
levels: list[str] = field(default_factory=list)
@dataclass
class NetworkResult:
"""Result from an SNMP network query."""
connection: str = "network"
ip: str = ""
product: str = "Unknown"
serial: str = ""
printer_status: str = ""
device_status: str = ""
display: str = ""
page_count: str = ""
supplies: SupplyReadings = field(default_factory=SupplyReadings)
error: str = ""
@dataclass
class SupplyStatus:
"""Processed supply level info for display."""
color: str
bar_text: str
status_text: str
warning: str
needs_replacement: bool