feat: discover printer by MAC (survives DHCP IP changes)

Add discover_printer(), _mac_of(), _port_open(), _sweep_arp(), _cache_ip()
with PRINTER_MAC/PRINTER_SUBNET/CACHE_FILE config + unit tests. Also adds
conftest.py to work around broken symlink that blocked pytest collection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mortdecai
2026-06-07 13:20:34 -04:00
parent fa580ec936
commit 7bfe862f53
4 changed files with 109 additions and 1 deletions
+66
View File
@@ -7,6 +7,7 @@ import os
import re
import json
import socket
import subprocess
import requests
import yfinance as yf
from datetime import datetime
@@ -31,6 +32,14 @@ REMOTE_HOST = config["remote_host"]
REMOTE_PASS = config["remote_pass"]
PRINTER_IP = config["printer_ip"]
PRINTER_PORT = int(config.get("printer_port", 9100))
from pathlib import Path
PRINTER_MAC = config.get("printer_mac", "").lower()
PRINTER_SUBNET = config.get("printer_subnet", "192.168.0")
CACHE_DIR = Path.home() / ".cache" / "pos-briefing"
CACHE_FILE = CACHE_DIR / "last_ip"
EASTERN_TZ = ZoneInfo("America/New_York")
# TM-m30 80mm paper, Font B — column width measured from test print
@@ -437,6 +446,63 @@ def build_receipt(articles, zfs, reddit, grafana, weather, finance):
return p.output
def _mac_of(ip):
subprocess.run(["ping", "-c1", "-W1", ip], capture_output=True)
out = subprocess.run(["ip", "neigh", "show", ip],
capture_output=True, text=True).stdout
m = re.search(r"lladdr ([0-9a-f:]{17})", out)
return m.group(1).lower() if m else None
def _port_open(ip, port, timeout=3):
try:
with socket.create_connection((ip, port), timeout=timeout):
return True
except OSError:
return False
def _sweep_arp():
"""Concurrent ping-prime the /24, then return {mac: ip} from the neigh table."""
procs = [subprocess.Popen(["ping", "-c1", "-W1", f"{PRINTER_SUBNET}.{i}"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for i in range(1, 255)]
for p in procs:
p.wait()
out = subprocess.run(["ip", "neigh", "show"],
capture_output=True, text=True).stdout
found = {}
for line in out.splitlines():
m = re.match(r"([\d.]+).*lladdr ([0-9a-f:]{17})", line)
if m:
found[m.group(2).lower()] = m.group(1)
return found
def _cache_ip(ip):
CACHE_DIR.mkdir(parents=True, exist_ok=True)
CACHE_FILE.write_text(ip)
def discover_printer():
"""Return the printer's current IP, resolved by MAC. Raise if not found."""
seeds = []
if CACHE_FILE.exists():
seeds.append(CACHE_FILE.read_text().strip())
if PRINTER_IP:
seeds.append(PRINTER_IP)
for ip in seeds:
if ip and _mac_of(ip) == PRINTER_MAC and _port_open(ip, PRINTER_PORT):
_cache_ip(ip)
return ip
if PRINTER_MAC:
ip = _sweep_arp().get(PRINTER_MAC)
if ip and _port_open(ip, PRINTER_PORT):
_cache_ip(ip)
return ip
raise RuntimeError(f"printer-not-found mac={PRINTER_MAC}")
def print_receipt(raw_bytes):
try:
with socket.create_connection((PRINTER_IP, PRINTER_PORT), timeout=10) as sock: