diff --git a/README.md b/README.md index f0b2ddc..cbe8adf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ $ pip install -r requirements.txt # Usage ```bash $ ./print.py --help -usage: print.py [-h] [-l {debug,info,warn,error}] [-b {mean-threshold,floyd-steinberg,atkinson,halftone,none}] [-s] [-d DEVICE] [-e ENERGY] +usage: print.py [-h] [-l {debug,info,warn,error}] [-b {mean-threshold,floyd-steinberg,atkinson,halftone,none}] [-s] [-d DEVICE] [-e ENERGY] [--no-resize] filename prints an image on your cat thermal printer @@ -40,6 +40,33 @@ options: services. -e ENERGY, --energy ENERGY Thermal energy. Between 0x0000 (light) and 0xffff (darker, default). + --no-resize Disable automatic image resizing. Small images print as-is, + large images scale to width without changing height. +``` + +# New Features + +## Image Resize Control + +The `--no-resize` option provides more control over image printing: + +- By default, images are resized to match printer width +- With `--no-resize`, small images print at original size +- Large images are scaled to width while maintaining aspect ratio + +Example: + +```bash +# Print without automatic resizing +./print.py --no-resize test.png +``` + +## Printer Keep-Awake +A keep-awake mechanism can be configured in the manager project to prevent printer sleep mode. This is typically managed externally and can be activated when running the server. + +```bash +# Managed by parent project +node server.js --keep-awake ``` # Example diff --git a/catprinter/ble.py b/catprinter/ble.py index e8e6af5..6303560 100644 --- a/catprinter/ble.py +++ b/catprinter/ble.py @@ -25,10 +25,10 @@ SCAN_TIMEOUT_S = 10 # Wait time after sending each chunk of data through BLE. -WAIT_AFTER_EACH_CHUNK_S = 0.02 +WAIT_AFTER_EACH_CHUNK_S = 0.2 # Wait for printer done event timeout. -WAIT_FOR_PRINTER_DONE_TIMEOUT = 30 +WAIT_FOR_PRINTER_DONE_TIMEOUT = 60 async def scan(name: Optional[str], timeout: int): diff --git a/catprinter/img.py b/catprinter/img.py index e37eb58..6a92bdd 100644 --- a/catprinter/img.py +++ b/catprinter/img.py @@ -120,19 +120,29 @@ def read_img( filename, print_width, img_binarization_algo, + no_resize=False ): + im = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) height = im.shape[0] width = im.shape[1] - factor = print_width / width - resized = cv2.resize( - im, - ( - print_width, - int(height * factor) - ), - interpolation=cv2.INTER_AREA) + if no_resize and width > print_width: + # With no_resize: only resize if image width exceeds print width + resized = cv2.resize( + im, + (print_width, int(height * width / print_width)), + interpolation=cv2.INTER_AREA + ) + else: + # Default behavior: resize to fit print width while maintaining aspect ratio + factor = print_width / width + resized = cv2.resize( + im, + (print_width, int(height * factor)), + interpolation=cv2.INTER_AREA + ) + if img_binarization_algo == 'atkinson': logger.info('⏳ Applying Atkinson dithering to image...') resized = atkinson_dither(resized) diff --git a/dummy-print.py b/dummy-print.py new file mode 100644 index 0000000..0eafd0b --- /dev/null +++ b/dummy-print.py @@ -0,0 +1,64 @@ +import asyncio +import contextlib +from typing import Optional +import uuid +from bleak import BleakClient, BleakScanner +from bleak.backends.device import BLEDevice +from catprinter import logger + +POSSIBLE_SERVICE_UUIDS = [ + "0000ae30-0000-1000-8000-00805f9b34fb", + "0000af30-0000-1000-8000-00805f9b34fb", +] + +TX_CHARACTERISTIC_UUID = "0000ae01-0000-1000-8000-00805f9b34fb" +RX_CHARACTERISTIC_UUID = "0000ae02-0000-1000-8000-00805f9b34fb" + +PRINTER_READY_NOTIFICATION = b"\x51\x78\xae\x01\x01\x00\x00\x00\xff" + +SCAN_TIMEOUT_S = 10 +WAIT_AFTER_EACH_CHUNK_S = 0.2 +WAIT_FOR_PRINTER_DONE_TIMEOUT = 60 + +# Scan and connect to the printer +async def scan(name: Optional[str], timeout: int): + autodiscover = not name + if autodiscover: + logger.info("⏳ Trying to auto-discover a printer...") + else: + logger.info(f"⏳ Looking for a BLE device named {name}...") + # Filter function for devices + def filter_fn(device: BLEDevice, adv_data): + if autodiscover: + return any(uuid in adv_data.service_uuids for uuid in POSSIBLE_SERVICE_UUIDS) + else: + return device.name == name + + device = await BleakScanner.find_device_by_filter(filter_fn, timeout=timeout) + if device is None: + raise RuntimeError("Unable to find printer, make sure it is turned on and in range") + logger.info(f"✅ Found printer: {device}") + return device + +# Simulate a dummy print activity (ping) +async def run_dummy_print(device: Optional[str]): + try: + address = await scan(device, timeout=SCAN_TIMEOUT_S) + except RuntimeError as e: + logger.error(f"🛑 {e}") + return + logger.info(f"⏳ Connecting to {address}...") + async with BleakClient(address) as client: + logger.info(f"✅ Connected: {client.is_connected}; MTU: {client.mtu_size}") + event = asyncio.Event() + + # Sending a dummy "ping" to the printer to keep it awake + logger.info("⏳ Sending dummy signal to the printer...") + await client.write_gatt_char(TX_CHARACTERISTIC_UUID, PRINTER_READY_NOTIFICATION) + await asyncio.sleep(WAIT_AFTER_EACH_CHUNK_S) + + logger.info("✅ Printer activity simulated successfully. Exiting.") + +# Run the dummy print to simulate the interaction with the printer +if __name__ == "__main__": + asyncio.run(run_dummy_print(None)) diff --git a/print.py b/print.py index ba47cc6..fc6d32c 100755 --- a/print.py +++ b/print.py @@ -32,12 +32,15 @@ def parse_args(): 'The printer\'s Bluetooth Low Energy (BLE) address ' '(MAC address on Linux; UUID on macOS) ' 'or advertisement name (e.g.: "GT01", "GB02", "GB03"). ' - 'If omitted, the the script will try to auto discover ' + 'If omitted, the script will try to auto discover ' 'the printer based on its advertised BLE services.' )) args.add_argument('-e', '--energy', type=lambda h: int(h.removeprefix("0x"), 16), help="Thermal energy. Between 0x0000 (light) and 0xffff (darker, default).", default="0xffff") + args.add_argument('--no-resize', action='store_true', + help='Disable automatic image resizing') + return args.parse_args() @@ -64,6 +67,7 @@ def main(): args.filename, PRINT_WIDTH, args.img_binarization_algo, + no_resize=args.no_resize ) if args.show_preview: show_preview(bin_img)