Files
Warren 55bca92691 V1.0: Class 1 fax — real-world 4-page send to external number confirmed
Core features:
- Class 1 T.30 protocol: full send/receive implementation
- HDLC: DLE-stuffing, FCS strip, USR5637 bit-reversal handling
- T.4 MH encoder/decoder (1728px A4 standard)
- Document pipeline: PDF (Ghostscript), PNG, TIFF input
- Width clamping: US Letter 1734px → 1728px fax standard
- Cover page: CJK rasterization (TW/CN/JP/EN), TIFF + HTML output
- OCR verification: Tesseract 5 with eng+chi_tra, CJK space-tolerant
- API server (axum): health, send, jobs, cover, retry, cancel
- Background worker: auto-poll queue, speed fallback, retry policy
- Modem detection, pool management

Real-world test results (2026-07-23):
- V90 → 25153038: 4 pages, V.17 12000 bps, 2:33 ✅
- USR5637 → 25153038: 4 pages, V.17 12000 bps, 2:26 ✅
- Both faxes confirmed received on remote machine

Tested: loopback (100% pixel match), multi-page, all input formats,
cover pages, OCR verify, API endpoints, worker processing.
13 unit tests pass, 0 new clippy warnings.
2026-07-24 18:47:15 +08:00

259 lines
18 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -e
# ═══════════════════════════════════════════════════════════════════════════
# telfax — End-to-End Fax Demo
# Shows the complete fax workflow over real POTS lines.
#
# Modem A (sender): /dev/cu.usbmodem00000021 (USR5637) → 2528-9852
# Modem B (receiver): /dev/cu.usbmodem123456781 (V90) → 2748-6656
# ═══════════════════════════════════════════════════════════════════════════
RECV_DEVICE="/dev/cu.usbmodem123456781" # V90 — answers calls
SEND_DEVICE="/dev/cu.usbmodem00000021" # USR5637 — dials out
RECV_NUMBER="27486656" # V90's phone number
OUTPUT_DIR="/tmp"
RESOLUTION="fine"
CLASS="1" # Class 1 HDLC protocol
# ── Timestamp for output files ──
STAMP=$(date +%s)
DEMO_DOC="/tmp/telfax_demo_doc_${STAMP}.tif"
COVER_PAGE="/tmp/telfax_cover_${STAMP}.tif"
PREVIEW_HTML="/tmp/telfax_demo_${STAMP}.html"
RECV_LOG="/tmp/telfax_recv_${STAMP}.log"
SEND_LOG="/tmp/telfax_send_${STAMP}.log"
echo ""
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ telfax — End-to-End Fax Demo ║"
echo "║ Real POTS Lines | Class 1 | V.27ter 4800 ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 1 — Create a demo document with visible content
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 1: Create a demo document (business letter)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
python3 "$(dirname "$0")/create_demo_doc.py" "$DEMO_DOC"
echo " ✓ Document saved: $DEMO_DOC"
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 2 — Add a cover page (sender/recipient info)
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 2: Add a cover page"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Generate cover page only (preview with both cover + doc)
# We'll use the preview command later which handles cover + doc together
echo " Cover parameters:"
echo " From: Jane Doe <jane@telfax.com>"
echo " To: John Smith <john@acme.com>"
echo " Subject: Business Proposal for Enterprise Fax Solution"
echo " Notes: Please review at your earliest convenience."
echo " ✓ Cover page will be auto-generated during send"
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 3 — Preview cover page + document
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 3: Preview cover page + document (HTML in browser)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
cargo run --release -- preview "$DEMO_DOC" \
--output "$PREVIEW_HTML" \
--resolution fine \
--to "John Smith <john@acme.com>" \
--from "Jane Doe <jane@telfax.com>" \
--subject "Business Proposal for Enterprise Fax Solution" \
--note "Please review at your earliest convenience." \
2>&1 | grep -v "^warning:\|^ Compiling\|^ Finished\|^ Running\|^$"
echo ""
echo " ✓ Preview saved to: $PREVIEW_HTML"
echo " ✓ First page = cover page, Second page = document"
echo ""
# Try to open in browser (macOS)
if command -v open &>/dev/null; then
echo " → Opening preview in browser..."
open "$PREVIEW_HTML" 2>/dev/null || true
fi
# ══════════════════════════════════════════════════════════════════════
# STEP 4 — Start receiver in background, then send
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 4 & 5: Send fax + Receive fax"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " ┌─ Sender ─────────────────────────────────────┐"
echo " │ Device: $SEND_DEVICE │"
echo " │ Number: 27486656 (V90) │"
echo " │ Doc: $DEMO_DOC │"
echo " │ Class: 1 (V.27ter 4800 bps) │"
echo " └──────────────────────────────────────────────┘"
echo " ┌─ Receiver ───────────────────────────────────┐"
echo " │ Device: $RECV_DEVICE │"
echo " │ Auto-answer: 2 rings │"
echo " │ Output: $OUTPUT_DIR │"
echo " │ Class: 1 │"
echo " └──────────────────────────────────────────────┘"
echo ""
# Kill any leftover processes
pkill -f "telfax.*receive" 2>/dev/null || true
sleep 2
# Start receiver (background, wait for call)
echo " → Starting receiver (V90 listening on 2748-6656)..."
RUST_LOG=telfax=info cargo run --release -- receive \
-d "$RECV_DEVICE" \
-c 1 \
-r 2 \
-o "$OUTPUT_DIR" \
> "$RECV_LOG" 2>&1 &
RECV_PID=$!
echo " ✓ Receiver PID: $RECV_PID"
# Wait for V90 to settle (auto-answer with ATS0=2 needs ~13s)
echo " → Waiting 40s for V90 to settle before dialing..."
sleep 40
# Send fax (foreground — shows live progress)
echo " → Sending fax (this takes ~60s)..."
echo ""
RUST_LOG=telfax=info cargo run --release -- send \
-d "$SEND_DEVICE" \
-c 1 \
-r "$RESOLUTION" \
--to "John Smith <john@acme.com>" \
--from "Jane Doe <jane@telfax.com>" \
--subject "Business Proposal for Enterprise Fax Solution" \
--note "Please review at your earliest convenience." \
"$RECV_NUMBER" "$DEMO_DOC" \
2>&1 | grep -v "^warning:\|^ Compiling\|^ Finished\|^ Running\|^$" \
| sed 's/.*telfax:/\t| telfax:/' \
| sed 's/.*telfax::/\t| telfax::/' \
|| true
echo ""
# Wait for receive to finish
echo " → Waiting for receiver to complete..."
wait $RECV_PID 2>/dev/null || true
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 6 — Show the status / progress
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 6: Transmission status"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " ┌─ SENDER LOG ────────────────────────────────────┐"
grep -E "Document:|training|Dummy frame|Sending page|Waiting for (CONNECT|OK)|error_rate|FRH CONNECT|MCF|DCN|sent successfully" "$SEND_LOG" \
| sed 's/.*telfax::/\t| telfax::/' \
| sed 's/.*telfax:/\t| telfax:/' || echo " (no sender log found)"
echo " └────────────────────────────────────────────────┘"
echo ""
echo " ┌─ RECEIVER LOG ──────────────────────────────────┐"
grep -E "Sending HDLC|FRH CONNECT|Received HDLC|error_rate|FRM CONNECT|FRM line.*CONNECT|Raw FRM|Found DLE|Destuffed|Received page|EOP|MCF|Hanging|TIFF saved|Decoding" "$RECV_LOG" \
| sed 's/.*telfax::/\t| telfax::/' \
| sed 's/.*telfax:/\t| telfax:/' || echo " (no receiver log found)"
echo " └────────────────────────────────────────────────┘"
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 7 — Show the received document
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 7: Received fax document"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
RECEIVED_TIF=$(grep "TIFF saved to" "$RECV_LOG" | sed 's/.*TIFF saved to //' | tr -d '[:space:]')
if [ -f "$RECEIVED_TIF" ]; then
SIZE=$(stat -f%z "$RECEIVED_TIF" 2>/dev/null || stat -c%s "$RECEIVED_TIF" 2>/dev/null)
echo " ✓ Received file: $RECEIVED_TIF"
echo " ✓ Size: $SIZE bytes"
# Generate a preview of the received pages for quick viewing
RECV_PREVIEW="/tmp/telfax_recv_preview_${STAMP}.html"
cargo run --release -- preview "$RECEIVED_TIF" --output "$RECV_PREVIEW" 2>&1 \
| grep -v "^warning:\|^ Compiling\|^ Finished\|^ Running\|^$" || true
echo ""
echo " ✓ Preview of received fax: $RECV_PREVIEW"
if command -v open &>/dev/null; then
open "$RECV_PREVIEW" 2>/dev/null || true
fi
else
echo " ⚠ No received TIFF found. Check receiver log: $RECV_LOG"
grep -E "error|Error|Error:|Timeout|Protocol|NoCarrier" "$RECV_LOG" \
| sed 's/.*telfax::/\t| /' || true
fi
echo ""
# ══════════════════════════════════════════════════════════════════════
# STEP 8 — Explain the whole flow
# ══════════════════════════════════════════════════════════════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " STEP 8: Protocol Flow Explanation"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " T.30 Class 1 fax transmission over analog POTS:"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ RECEIVER (V90) SENDER (USR5637) │"
echo " │ ────────────── ──────────────── │"
echo " │ │"
echo " │ 1. Auto-answer (2 rings) Dial 27486656 │"
echo " │ ├────── CED (2100 Hz) ──────┤ │"
echo " │ ├────── DIS (HDLC) ─────────┤ │"
echo " │ ├────── DCS (HDLC) ─────────┤ │"
echo " │ │ negotiate V.27ter 4800 │ │"
echo " │ ├────── TCF (FRM) ──────────┤ FTM 1.5s zeros │"
echo " │ │ ✓ CONNECT + receive │ │"
echo " │ ├────── CFR (HDLC) ─────────┤ FTH (HDLC) │"
echo " │ │ ├── dummy FTH (HDLC) │"
echo " │ 2. FRM (enter before training) FTM page data │"
echo " │ ├────── Page Data ──────────┤ V.27ter 4800 bps │"
echo " │ │ ✓ CONNECT + receive │ 14,599 bytes MH │
echo " │ ├────── EOP (HDLC) ─────────┤ FRH │"
echo " │ ├────── MCF (HDLC) ─────────┤ FTH │"
echo " │ ├────── DCN (HDLC) ─────────┤ │"
echo " │ │"
echo " │ Total: 1 page, 14636 bytes MH, TIFF saved to disk │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " Key engineering details:"
echo " ─────────────────────────"
echo " • V.27ter 4800 bps (fine resolution ~204×196 dpi)"
echo " • Real POTS lines — no VoIP, no T.38"
echo " • HDLC framing with CRC-16 (DLE-stuffed)"
echo " • MH (Modified Huffman) run-length compression"
echo " • Zero-delay FRM: V90 must catch training from start"
echo " • DLE-ETX end-marker detection via rposition"
echo ""
echo ""
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ Demo complete! ║"
echo "║ ║"
echo "║ All logs: ║"
echo "║ Sender: $SEND_LOG ║"
echo "║ Receiver: $RECV_LOG ║"
echo "║ ║"
echo "║ Outputs: ║"
echo "║ Preview: $PREVIEW_HTML ║"
echo "║ Received: $RECEIVED_TIF ║"
echo "║ Demo doc: $DEMO_DOC ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo ""