Initial commit: Brother QL USB-to-Network Bridge
- TCP server exposing USB Brother QL printers on port 9100 - Supports all Brother QL series label printers - Async architecture for handling concurrent print jobs - Raspberry Pi deployment script and systemd service - Documentation for setup and InvenTree integration
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# uv
|
||||||
|
uv.lock
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
183
README.md
Normal file
183
README.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Brother QL Bridge
|
||||||
|
|
||||||
|
A TCP-to-USB bridge that exposes a USB-connected Brother QL label printer on the network, making it compatible with network-based printing tools like InvenTree's Brother plugin.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
- Listens on TCP port 9100 (standard raw printing port)
|
||||||
|
- Receives print jobs from network clients
|
||||||
|
- Forwards the raw Brother raster data to a USB-connected QL printer
|
||||||
|
|
||||||
|
## Supported Printers
|
||||||
|
|
||||||
|
- QL-500, QL-550, QL-560, QL-570, QL-580N
|
||||||
|
- QL-650TD, QL-700, QL-710W, QL-720NW
|
||||||
|
- QL-800, QL-810W, QL-820NWB
|
||||||
|
- QL-1050, QL-1060N
|
||||||
|
|
||||||
|
## Raspberry Pi Deployment
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. Raspberry Pi with Raspberry Pi OS (Bookworm or later recommended)
|
||||||
|
2. Brother QL printer connected via USB
|
||||||
|
3. Python 3.10+
|
||||||
|
|
||||||
|
### Step 1: Install System Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3 python3-pip python3-venv libusb-1.0-0-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Clone/Copy the Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create project directory
|
||||||
|
mkdir -p ~/brother-ql-bridge
|
||||||
|
cd ~/brother-ql-bridge
|
||||||
|
|
||||||
|
# Copy files: bridge.py, config.py, pyproject.toml, requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Setup Python Environment
|
||||||
|
|
||||||
|
Using `uv` (recommended):
|
||||||
|
```bash
|
||||||
|
# Install uv if not already installed
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
|
# Create venv and install dependencies
|
||||||
|
cd ~/brother-ql-bridge
|
||||||
|
uv venv
|
||||||
|
uv pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using standard pip:
|
||||||
|
```bash
|
||||||
|
cd ~/brother-ql-bridge
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Configure USB Permissions
|
||||||
|
|
||||||
|
Create a udev rule to allow non-root access to Brother printers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo tee /etc/udev/rules.d/99-brother-ql.rules << 'EOF'
|
||||||
|
# Brother QL label printers
|
||||||
|
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", MODE="0666"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Test the Bridge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/brother-ql-bridge
|
||||||
|
source .venv/bin/activate
|
||||||
|
python bridge.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see output like:
|
||||||
|
```
|
||||||
|
2024-12-21 16:30:00 [INFO] Found printer: QL-570
|
||||||
|
2024-12-21 16:30:00 [INFO] USB connection established
|
||||||
|
2024-12-21 16:30:00 [INFO] Bridge server listening on ('0.0.0.0', 9100)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Install as Systemd Service
|
||||||
|
|
||||||
|
Create the service file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo tee /etc/systemd/system/brother-ql-bridge.service << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Brother QL USB-to-Network Bridge
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=$USER
|
||||||
|
WorkingDirectory=$HOME/brother-ql-bridge
|
||||||
|
ExecStart=$HOME/brother-ql-bridge/.venv/bin/python bridge.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and start:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable brother-ql-bridge
|
||||||
|
sudo systemctl start brother-ql-bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
Check status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status brother-ql-bridge
|
||||||
|
sudo journalctl -u brother-ql-bridge -f # Follow logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `config.py` to customize:
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `TCP_HOST` | `0.0.0.0` | Listen address (0.0.0.0 = all interfaces) |
|
||||||
|
| `TCP_PORT` | `9100` | TCP port for incoming print jobs |
|
||||||
|
| `USB_DEVICE` | `None` | USB device path (None = auto-detect) |
|
||||||
|
| `PRINTER_MODEL` | `QL-570` | Printer model for compatibility |
|
||||||
|
| `LOG_LEVEL` | `INFO` | Logging verbosity |
|
||||||
|
|
||||||
|
## Command Line Options
|
||||||
|
|
||||||
|
```
|
||||||
|
python bridge.py [-h] [-p PORT] [-H HOST] [-d DEVICE] [-v]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-p, --port PORT TCP port (default: 9100)
|
||||||
|
-H, --host HOST Bind address (default: 0.0.0.0)
|
||||||
|
-d, --device DEVICE USB device (e.g., usb://0x04f9:0x2028)
|
||||||
|
-v, --verbose Enable debug logging
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using with InvenTree
|
||||||
|
|
||||||
|
Configure InvenTree's Brother label plugin with:
|
||||||
|
- **IP Address**: Your Raspberry Pi's IP address
|
||||||
|
- **Port**: 9100 (or your configured port)
|
||||||
|
- **Model**: Your printer model (e.g., QL-570)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "No Brother QL printer found on USB"
|
||||||
|
|
||||||
|
1. Check USB connection: `lsusb | grep Brother`
|
||||||
|
2. Verify udev rules are loaded: `sudo udevadm control --reload-rules`
|
||||||
|
3. Unplug and replug the printer
|
||||||
|
|
||||||
|
### Permission denied errors
|
||||||
|
|
||||||
|
Ensure udev rules are in place and you've re-plugged the printer after adding them.
|
||||||
|
|
||||||
|
### Service won't start
|
||||||
|
|
||||||
|
Check logs: `sudo journalctl -u brother-ql-bridge -n 50`
|
||||||
|
|
||||||
|
### Port 9100 in use
|
||||||
|
|
||||||
|
Either change the port in config.py or stop the conflicting service:
|
||||||
|
```bash
|
||||||
|
sudo lsof -i :9100
|
||||||
|
```
|
||||||
261
bridge.py
Normal file
261
bridge.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Brother QL Bridge - USB to Network Bridge
|
||||||
|
|
||||||
|
Exposes a USB-connected Brother QL printer (e.g., QL-570) on the network
|
||||||
|
so it can be used with network-based printing tools like InvenTree's Brother plugin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import usb.core
|
||||||
|
import usb.util
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
|
# Brother printer USB vendor ID
|
||||||
|
BROTHER_VENDOR_ID = 0x04F9
|
||||||
|
|
||||||
|
# Known QL printer product IDs
|
||||||
|
PRODUCT_IDS = {
|
||||||
|
0x2015: "QL-500",
|
||||||
|
0x2016: "QL-550",
|
||||||
|
0x2027: "QL-560",
|
||||||
|
0x2028: "QL-570",
|
||||||
|
0x2029: "QL-580N",
|
||||||
|
0x201B: "QL-650TD",
|
||||||
|
0x2042: "QL-700",
|
||||||
|
0x2043: "QL-710W",
|
||||||
|
0x2044: "QL-720NW",
|
||||||
|
0x209B: "QL-800",
|
||||||
|
0x209C: "QL-810W",
|
||||||
|
0x209D: "QL-820NWB",
|
||||||
|
0x2020: "QL-1050",
|
||||||
|
0x202A: "QL-1060N",
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = logging.getLogger("brother-ql-bridge")
|
||||||
|
|
||||||
|
|
||||||
|
class USBPrinter:
|
||||||
|
"""Handles USB communication with the Brother QL printer."""
|
||||||
|
|
||||||
|
def __init__(self, device=None):
|
||||||
|
self.dev = None
|
||||||
|
self.ep_out = None
|
||||||
|
self.ep_in = None
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
|
if device:
|
||||||
|
# Parse device string like "usb://0x04f9:0x2028"
|
||||||
|
if device.startswith("usb://"):
|
||||||
|
parts = device[6:].split(":")
|
||||||
|
vendor = int(parts[0], 16)
|
||||||
|
product = int(parts[1].split("/")[0], 16)
|
||||||
|
self.dev = usb.core.find(idVendor=vendor, idProduct=product)
|
||||||
|
else:
|
||||||
|
# Auto-detect Brother QL printer
|
||||||
|
self.dev = usb.core.find(idVendor=BROTHER_VENDOR_ID)
|
||||||
|
|
||||||
|
if self.dev is None:
|
||||||
|
raise RuntimeError("No Brother QL printer found on USB")
|
||||||
|
|
||||||
|
product_name = PRODUCT_IDS.get(self.dev.idProduct, f"Unknown (0x{self.dev.idProduct:04x})")
|
||||||
|
logger.info(f"Found printer: {product_name}")
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Initialize USB connection to the printer."""
|
||||||
|
# Detach kernel driver if necessary
|
||||||
|
try:
|
||||||
|
if self.dev.is_kernel_driver_active(0):
|
||||||
|
self.dev.detach_kernel_driver(0)
|
||||||
|
logger.debug("Detached kernel driver")
|
||||||
|
except (usb.core.USBError, NotImplementedError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Set configuration
|
||||||
|
try:
|
||||||
|
self.dev.set_configuration()
|
||||||
|
except usb.core.USBError:
|
||||||
|
pass # Already configured
|
||||||
|
|
||||||
|
# Get endpoints
|
||||||
|
cfg = self.dev.get_active_configuration()
|
||||||
|
intf = cfg[(0, 0)]
|
||||||
|
|
||||||
|
self.ep_out = usb.util.find_descriptor(
|
||||||
|
intf,
|
||||||
|
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress)
|
||||||
|
== usb.util.ENDPOINT_OUT,
|
||||||
|
)
|
||||||
|
self.ep_in = usb.util.find_descriptor(
|
||||||
|
intf,
|
||||||
|
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress)
|
||||||
|
== usb.util.ENDPOINT_IN,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.ep_out is None:
|
||||||
|
raise RuntimeError("Could not find USB OUT endpoint")
|
||||||
|
|
||||||
|
logger.info("USB connection established")
|
||||||
|
|
||||||
|
def write_sync(self, data: bytes) -> int:
|
||||||
|
"""Write data to the printer synchronously with proper timeout."""
|
||||||
|
# Write in chunks with longer timeout (15 seconds)
|
||||||
|
chunk_size = 32768
|
||||||
|
total_written = 0
|
||||||
|
for i in range(0, len(data), chunk_size):
|
||||||
|
chunk = data[i : i + chunk_size]
|
||||||
|
written = self.ep_out.write(chunk, timeout=15000)
|
||||||
|
total_written += written
|
||||||
|
return total_written
|
||||||
|
|
||||||
|
async def write(self, data: bytes) -> int:
|
||||||
|
"""Write data to the printer asynchronously."""
|
||||||
|
async with self._lock:
|
||||||
|
try:
|
||||||
|
return await asyncio.get_event_loop().run_in_executor(
|
||||||
|
None, self.write_sync, data
|
||||||
|
)
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
logger.error(f"USB write error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Release USB resources."""
|
||||||
|
if self.dev:
|
||||||
|
usb.util.dispose_resources(self.dev)
|
||||||
|
|
||||||
|
|
||||||
|
class PrinterBridge:
|
||||||
|
"""TCP to USB bridge server."""
|
||||||
|
|
||||||
|
def __init__(self, printer: USBPrinter, host: str, port: int):
|
||||||
|
self.printer = printer
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.server = None
|
||||||
|
self.job_count = 0
|
||||||
|
|
||||||
|
async def handle_client(
|
||||||
|
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
||||||
|
):
|
||||||
|
"""Handle incoming print job from TCP client."""
|
||||||
|
addr = writer.get_extra_info("peername")
|
||||||
|
self.job_count += 1
|
||||||
|
job_id = self.job_count
|
||||||
|
logger.info(f"[Job {job_id}] Connection from {addr}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Buffer entire job first (Brother raster protocol works best this way)
|
||||||
|
chunks = []
|
||||||
|
while True:
|
||||||
|
data = await reader.read(4096)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
chunks.append(data)
|
||||||
|
|
||||||
|
job_data = b"".join(chunks)
|
||||||
|
logger.info(f"[Job {job_id}] Received {len(job_data)} bytes, sending to printer...")
|
||||||
|
|
||||||
|
# Send complete job to printer
|
||||||
|
await self.printer.write(job_data)
|
||||||
|
logger.info(f"[Job {job_id}] Completed successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Job {job_id}] Error: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
"""Start the bridge server."""
|
||||||
|
self.server = await asyncio.start_server(
|
||||||
|
self.handle_client, self.host, self.port
|
||||||
|
)
|
||||||
|
|
||||||
|
addrs = ", ".join(str(sock.getsockname()) for sock in self.server.sockets)
|
||||||
|
logger.info(f"Bridge server listening on {addrs}")
|
||||||
|
|
||||||
|
async with self.server:
|
||||||
|
await self.server.serve_forever()
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
"""Stop the bridge server."""
|
||||||
|
if self.server:
|
||||||
|
self.server.close()
|
||||||
|
await self.server.wait_closed()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Bridge a USB Brother QL printer to the network"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--port",
|
||||||
|
type=int,
|
||||||
|
default=config.TCP_PORT,
|
||||||
|
help=f"TCP port to listen on (default: {config.TCP_PORT})"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-H", "--host",
|
||||||
|
default=config.TCP_HOST,
|
||||||
|
help=f"Host/IP to bind to (default: {config.TCP_HOST})"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d", "--device",
|
||||||
|
default=config.USB_DEVICE,
|
||||||
|
help="USB device (e.g., usb://0x04f9:0x2028). Auto-detect if not specified"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable verbose logging"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
log_level = logging.DEBUG if args.verbose else getattr(logging, config.LOG_LEVEL)
|
||||||
|
logging.basicConfig(
|
||||||
|
level=log_level,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize printer
|
||||||
|
try:
|
||||||
|
printer = USBPrinter(args.device)
|
||||||
|
printer.connect()
|
||||||
|
except RuntimeError as e:
|
||||||
|
logger.error(f"Failed to initialize printer: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create and run bridge
|
||||||
|
bridge = PrinterBridge(printer, args.host, args.port)
|
||||||
|
|
||||||
|
# Handle shutdown signals
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
logger.info("Shutting down...")
|
||||||
|
loop.create_task(bridge.stop())
|
||||||
|
|
||||||
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||||
|
loop.add_signal_handler(sig, shutdown)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await bridge.start()
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
printer.close()
|
||||||
|
logger.info("Bridge stopped")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
15
config.py
Normal file
15
config.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""Configuration for Brother QL Bridge."""
|
||||||
|
|
||||||
|
# TCP server settings
|
||||||
|
TCP_HOST = "0.0.0.0" # Listen on all interfaces
|
||||||
|
TCP_PORT = 9100 # Standard raw printing port
|
||||||
|
|
||||||
|
# USB printer settings
|
||||||
|
# Set to None for auto-detection, or specify like "usb://0x04f9:0x2028"
|
||||||
|
USB_DEVICE = None
|
||||||
|
|
||||||
|
# Printer model (used for brother_ql compatibility)
|
||||||
|
PRINTER_MODEL = "QL-570"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL = "INFO"
|
||||||
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "brother-ql-bridge"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Bridge a USB Brother QL printer to the network"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = [
|
||||||
|
"brother_ql>=0.9.4",
|
||||||
|
"pyusb>=1.2.1",
|
||||||
|
]
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
brother_ql>=0.9.4
|
||||||
|
pyusb>=1.2.1
|
||||||
95
scripts/deploy.sh
Executable file
95
scripts/deploy.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Brother QL Bridge - Raspberry Pi Deployment Script
|
||||||
|
set -e
|
||||||
|
|
||||||
|
INSTALL_DIR="${INSTALL_DIR:-$HOME/brother-ql-bridge}"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
echo "=== Brother QL Bridge Deployment ==="
|
||||||
|
echo "Install directory: $INSTALL_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running on Raspberry Pi
|
||||||
|
if [[ ! -f /proc/device-tree/model ]] || ! grep -q "Raspberry Pi" /proc/device-tree/model 2>/dev/null; then
|
||||||
|
echo "Warning: This doesn't appear to be a Raspberry Pi"
|
||||||
|
read -p "Continue anyway? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 1: Install system dependencies
|
||||||
|
echo "[1/5] Installing system dependencies..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3 python3-pip python3-venv libusb-1.0-0-dev
|
||||||
|
|
||||||
|
# Step 2: Install uv if not present
|
||||||
|
echo "[2/5] Setting up Python package manager..."
|
||||||
|
if ! command -v uv &> /dev/null; then
|
||||||
|
echo "Installing uv..."
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: Setup project
|
||||||
|
echo "[3/5] Setting up project..."
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
cp "$PROJECT_DIR/bridge.py" "$INSTALL_DIR/"
|
||||||
|
cp "$PROJECT_DIR/config.py" "$INSTALL_DIR/"
|
||||||
|
cp "$PROJECT_DIR/requirements.txt" "$INSTALL_DIR/"
|
||||||
|
cp "$PROJECT_DIR/pyproject.toml" "$INSTALL_DIR/"
|
||||||
|
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
uv venv
|
||||||
|
uv pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Step 4: Setup udev rules
|
||||||
|
echo "[4/5] Configuring USB permissions..."
|
||||||
|
if [[ ! -f /etc/udev/rules.d/99-brother-ql.rules ]]; then
|
||||||
|
sudo tee /etc/udev/rules.d/99-brother-ql.rules > /dev/null << 'EOF'
|
||||||
|
# Brother QL label printers
|
||||||
|
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", MODE="0666"
|
||||||
|
EOF
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
echo "USB permissions configured. You may need to unplug and replug the printer."
|
||||||
|
else
|
||||||
|
echo "USB permissions already configured."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 5: Install systemd service
|
||||||
|
echo "[5/5] Installing systemd service..."
|
||||||
|
sudo tee /etc/systemd/system/brother-ql-bridge.service > /dev/null << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Brother QL USB-to-Network Bridge
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=$USER
|
||||||
|
WorkingDirectory=$INSTALL_DIR
|
||||||
|
ExecStart=$INSTALL_DIR/.venv/bin/python bridge.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable brother-ql-bridge
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deployment Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "To start the service:"
|
||||||
|
echo " sudo systemctl start brother-ql-bridge"
|
||||||
|
echo ""
|
||||||
|
echo "To check status:"
|
||||||
|
echo " sudo systemctl status brother-ql-bridge"
|
||||||
|
echo ""
|
||||||
|
echo "To view logs:"
|
||||||
|
echo " sudo journalctl -u brother-ql-bridge -f"
|
||||||
|
echo ""
|
||||||
|
echo "To test manually:"
|
||||||
|
echo " cd $INSTALL_DIR && source .venv/bin/activate && python bridge.py -v"
|
||||||
Reference in New Issue
Block a user