#!/usr/bin/env python3 import sys import os import json import base64 from io import BytesIO from random import randint import argparse # Dependency Check MISSING_DEPS = [] try: import requests except ImportError: MISSING_DEPS.append("requests") try: from PIL import Image as PILImg, ImageOps except ImportError: MISSING_DEPS.append("Pillow (PIL)") if MISSING_DEPS: print(f"Error: Missing dependencies: {', '.join(MISSING_DEPS)}") print("Please install them using: pip install " + " ".join(["requests", "Pillow"])) sys.exit(1) # Default IP for Moritz's Memobird DEFAULT_HOST = "192.168.10.165" def to_byte_array(img): imgByteArr = BytesIO() img.save(imgByteArr, format='bmp') return imgByteArr.getvalue() class MemobirdSender: def __init__(self, host=DEFAULT_HOST, port='80'): self.host = host self.port = port self.uri = f"http://{self.host}:{self.port}/sys/printer" def send(self, text_list, print_id=None): print_id = print_id or randint(1, 10**6) payload = { 'command': 3, 'content': { 'textList': text_list }, 'encryptFlag': 0, 'hasHead': 0, 'hasSignature': 0, 'hasTail': 0, 'isFromDirectPrint': False, 'msgType': 1, 'pkgCount': 1, 'pkgNo': 1, 'printID': print_id, 'priority': 0, 'result': 0, 'scripType': 3 } try: resp = requests.post(self.uri, json=payload, timeout=10) resp.raise_for_status() return resp.status_code except requests.exceptions.ConnectTimeout: print(f"Error: Connection to Memobird at {self.host} timed out.") print("Check if the printer is powered on and on the same network.") sys.exit(1) except requests.exceptions.ConnectionError: print(f"Error: Could not connect to Memobird at {self.host}.") print("Verify the IP address and network connectivity.") sys.exit(1) except Exception as e: print(f"Error sending to Memobird: {e}") sys.exit(1) def create_text_element(text, big=False, bold=False, underline=False): if not text.endswith('\n'): text += '\n' return { 'encodeType': 0, 'printType': 1, 'basetext': base64.b64encode(text.encode("GBK")).decode(), 'fontSize': 1 + big, 'bold': 1 * bold, 'underline': 1 * underline } def create_image_element(path): img = PILImg.open(path) img = img.convert(mode='L') img = ImageOps.flip(img) img = img.convert(mode='1') return { 'encodeType': 0, 'printType': 5, 'basetext': base64.b64encode(to_byte_array(img)).decode() } def create_line_element(linetype='THICK'): codes = {'THICK': 41, 'THIN': 42, 'DASH': 43} return { 'encodeType': 0, 'printType': 4, 'iconID': codes.get(linetype, 41) } def main(): parser = argparse.ArgumentParser(description="Print to Memobird") parser.add_argument("--text", help="Text to print") parser.add_argument("--image", help="Path to image to print") parser.add_argument("--ip", default=DEFAULT_HOST, help=f"Memobird IP (default: {DEFAULT_HOST})") parser.add_argument("--big", action="store_true", help="Use big font") parser.add_argument("--bold", action="store_true", help="Use bold font") parser.add_argument("--underline", action="store_true", help="Use underline") parser.add_argument("--line", choices=['THICK', 'THIN', 'DASH'], help="Print a separator line") args = parser.parse_args() if not args.text and not args.image and not args.line: parser.print_help() sys.exit(1) sender = MemobirdSender(host=args.ip) elements = [] if args.line: elements.append(create_line_element(args.line)) if args.text: elements.append(create_text_element(args.text, big=args.big, bold=args.bold, underline=args.underline)) if args.image: if os.path.exists(args.image): elements.append(create_image_element(args.image)) else: print(f"Error: Image not found: {args.image}") sys.exit(1) status = sender.send(elements) print(f"Success! Printer returned {status}") if __name__ == "__main__": main()