diff --git a/.gitignore b/.gitignore index 5a602cf..4522f87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ __pycache__ *~ +build/ +.eggs/ +eggs/ +*.egg-info/ +*.egg \ No newline at end of file diff --git a/README.md b/README.md index ab9c0be..8a53778 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,16 @@ Copyright (C) 2023 Vladimir Vukicevic ## Usage -Python 3 is required. Use `pip install -r requirements.txt` to install dependencies. +Python 3 is required. + +Clone and install via pip: `pip install .` + +Install directly from git via pipx: `pipx install git+https://github.com/vvuk/cassini.git` ### Printer status ``` -$ ./cassini.py status +$ cassini status 192.168.7.128: Saturn3Ultra (ELEGOO Saturn 3 Ultra) Status: 1 Print Status: 2 Layers: 19/130 File Transfer Status: 0 @@ -30,7 +34,7 @@ $ ./cassini.py status ### Printer(s) full status ``` -$ ./cassini.py status-full +$ cassini status-full ``` Will print out the full json status of all printers found. @@ -39,14 +43,14 @@ Will print out the full json status of all printers found. ### Watch live print progress ``` -$ ./cassini.py watch [interval] +$ cassini watch [interval] _STL_B_Warriors_1_Sword_Combined_Supported.goo |███████████████████████████████████▉ ︎ | 90% ``` ### File transfer ``` -$ ./cassini.py [--printer printer_ip] upload MyFile.goo +$ cassini [--printer printer_ip] upload MyFile.goo 15:39:15,190 INFO: Using printer Saturn3Ultra (ELEGOO Saturn 3 Ultra) MyFile.goo |████████████████████████████████████████| 100% [5750174/5750174] (3291238.22/s) ``` @@ -54,7 +58,7 @@ MyFile.goo |██████████████████████ ### Start a print (of an existing file) ``` -$ ./cassini.py [--printer printer_ip] print Myfile.goo +$ cassini [--printer printer_ip] print Myfile.goo ``` ### Connect printer(s) to particular MQTT server @@ -168,14 +172,14 @@ The printer subscribes to a request topic specific to its mainboard ID `/sdcp/re Commands discovered: -| ID | Description | Data | -|-----|-------------|------| -| 0 | Unknown. Sent by CHITUBOX first. | None | -| 1 | Unknown. Sent by CHITUBOX after 0. | None | -| 64 | Maybe a disconnect? | None | -| 128 | Start printing. | See below. | -| 256 | Upload file. | See below. | -| 512 | Set some kind of time period. | `{ "TimePeriod": 5000 }` | +| ID | Description | Data | +| --- | ---------------------------------- | ------------------------ | +| 0 | Unknown. Sent by CHITUBOX first. | None | +| 1 | Unknown. Sent by CHITUBOX after 0. | None | +| 64 | Maybe a disconnect? | None | +| 128 | Start printing. | See below. | +| 256 | Upload file. | See below. | +| 512 | Set some kind of time period. | `{ "TimePeriod": 5000 }` | #### 128: Start Printing diff --git a/cassini/__init__.py b/cassini/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cassini.py b/cassini/cassini.py similarity index 64% rename from cassini.py rename to cassini/cassini.py index 926340b..7c8f676 100755 --- a/cassini.py +++ b/cassini/cassini.py @@ -14,9 +14,9 @@ import asyncio import logging import argparse -from simple_mqtt_server import SimpleMQTTServer -from simple_http_server import SimpleHTTPServer -from saturn_printer import SaturnPrinter, PrintInfoStatus, CurrentStatus, FileStatus +from .simple_mqtt_server import SimpleMQTTServer +from .simple_http_server import SimpleHTTPServer +from .saturn_printer import SaturnPrinter, PrintInfoStatus, CurrentStatus, FileStatus logging.basicConfig( level=logging.INFO, @@ -28,35 +28,42 @@ from alive_progress import alive_bar except ImportError: logging.info("Run 'pip3 install alive-progress' for better progress bars") + class alive_bar(object): def __init__(self, total, title, **kwargs): self.total = total self.title = title + def __call__(self, x): print(f"{int(x*self.total)}/{self.total} {self.title}\r", end="") + def __enter__(self): return self + def __exit__(self, *args): print("\n") + async def create_mqtt_server(): - mqtt = SimpleMQTTServer('0.0.0.0', 0) + mqtt = SimpleMQTTServer("0.0.0.0", 0) await mqtt.start() mqtt_server_task = asyncio.create_task(mqtt.serve_forever()) return mqtt, mqtt.port, mqtt_server_task + async def create_http_server(): - http = SimpleHTTPServer('0.0.0.0', 0) + http = SimpleHTTPServer("0.0.0.0", 0) await http.start() http_server_task = asyncio.create_task(http.serve_forever()) return http, http.port, http_server_task + def do_status(printers): for i, p in enumerate(printers): - attrs = p.desc['Data']['Attributes'] - status = p.desc['Data']['Status'] - print_info = status['PrintInfo'] - file_info = status['FileTransferInfo'] + attrs = p.desc["Data"]["Attributes"] + status = p.desc["Data"]["Status"] + print_info = status["PrintInfo"] + file_info = status["FileTransferInfo"] print(f"{p.addr[0]}:") print(f" {attrs['Name']} ({attrs['MachineName']})") print(f" Machine Status: {CurrentStatus(status['CurrentStatus']).name}") @@ -65,6 +72,7 @@ def do_status(printers): print(f" File: {print_info['Filename']}") print(f" File Transfer Status: {FileStatus(file_info['Status']).name}") + def do_status_full(printers): for i, p in enumerate(printers): pprint.pprint(p.desc) @@ -72,23 +80,30 @@ def do_status_full(printers): def do_watch(printer, interval=5, broadcast=None): status = printer.status() - with alive_bar(total=status['totalLayers'], manual=True, elapsed=False, title=status['filename']) as bar: + with alive_bar( + total=status["totalLayers"], + manual=True, + elapsed=False, + title=status["filename"], + ) as bar: while True: printers = SaturnPrinter.find_printers(broadcast=broadcast) if len(printers) > 0: status = printers[0].status() - pct = status['currentLayer'] / status['totalLayers'] + pct = status["currentLayer"] / status["totalLayers"] bar(pct) if pct >= 1.0: break time.sleep(interval) + async def create_servers(): mqtt, mqtt_port, mqtt_task = await create_mqtt_server() http, http_port, http_task = await create_http_server() return mqtt, http + async def do_print(printer, filename): mqtt, http = await create_servers() connected = await printer.connect(mqtt, http) @@ -103,6 +118,7 @@ async def do_print(printer, filename): logging.error("Failed to start print") sys.exit(1) + async def do_upload(printer, filename, start_printing=False): if not os.path.exists(filename): logging.error(f"{filename} does not exist") @@ -113,11 +129,13 @@ async def do_upload(printer, filename, start_printing=False): if not connected: logging.error("Failed to connect to printer") sys.exit(1) - - #await printer.upload_file(filename, start_printing=start_printing) - upload_task = asyncio.create_task(printer.upload_file(filename, start_printing=start_printing)) + + # await printer.upload_file(filename, start_printing=start_printing) + upload_task = asyncio.create_task( + printer.upload_file(filename, start_printing=start_printing) + ) # grab the first one, because we want the file size - basename = filename.split('\\')[-1].split('/')[-1] + basename = filename.split("\\")[-1].split("/")[-1] file_size = os.path.getsize(filename) with alive_bar(total=file_size, manual=True, elapsed=False, title=basename) as bar: while True: @@ -133,29 +151,51 @@ async def do_upload(printer, filename, start_printing=False): break await upload_task + def main(): - parser = argparse.ArgumentParser(prog='cassini', description='ELEGOO Saturn printer control utility') - parser.add_argument('-p', '--printer', help='ID of printer to target') - parser.add_argument('--broadcast', help='Explicit broadcast IP address') - parser.add_argument('--debug', help='Enable debug logging', action='store_true') + parser = argparse.ArgumentParser( + prog="cassini", description="ELEGOO Saturn printer control utility" + ) + parser.add_argument("-p", "--printer", help="ID of printer to target") + parser.add_argument("--broadcast", help="Explicit broadcast IP address") + parser.add_argument("--debug", help="Enable debug logging", action="store_true") subparsers = parser.add_subparsers(title="commands", dest="command", required=True) - parser_status = subparsers.add_parser('status', help='Discover and display status of all printers') - parser_status_full = subparsers.add_parser('status-full', help='Discover and display full status of all printers') - - parser_watch = subparsers.add_parser('watch', help='Continuously update the status of the selected printer') - parser_watch.add_argument('--interval', type=int, help='Status update interval (seconds)', default=5) - - parser_upload = subparsers.add_parser('upload', help='Upload a file to the printer') - parser_upload.add_argument('--start-printing', help='Start printing after upload is complete', action='store_true') - parser_upload.add_argument('filename', help='File to upload') - - parser_print = subparsers.add_parser('print', help='Start printing a file already present on the printer') - parser_print.add_argument('filename', help='File to print') - - parser_connect_mqtt = subparsers.add_parser('connect-mqtt', help='Connect printer to particular MQTT server') - parser_connect_mqtt.add_argument('address', help='MQTT host and port, e.g. "192.168.1.33:1883" or "mqtt.local:1883"') + parser_status = subparsers.add_parser( + "status", help="Discover and display status of all printers" + ) + parser_status_full = subparsers.add_parser( + "status-full", help="Discover and display full status of all printers" + ) + + parser_watch = subparsers.add_parser( + "watch", help="Continuously update the status of the selected printer" + ) + parser_watch.add_argument( + "--interval", type=int, help="Status update interval (seconds)", default=5 + ) + + parser_upload = subparsers.add_parser("upload", help="Upload a file to the printer") + parser_upload.add_argument( + "--start-printing", + help="Start printing after upload is complete", + action="store_true", + ) + parser_upload.add_argument("filename", help="File to upload") + + parser_print = subparsers.add_parser( + "print", help="Start printing a file already present on the printer" + ) + parser_print.add_argument("filename", help="File to print") + + parser_connect_mqtt = subparsers.add_parser( + "connect-mqtt", help="Connect printer to particular MQTT server" + ) + parser_connect_mqtt.add_argument( + "address", + help='MQTT host and port, e.g. "192.168.1.33:1883" or "mqtt.local:1883"', + ) args = parser.parse_args() @@ -187,7 +227,7 @@ def main(): sys.exit(0) if args.command == "connect-mqtt": - mqtt_host, mqtt_port = args.address.split(':') + mqtt_host, mqtt_port = args.address.split(":") try: mqtt_host = socket.gethostbyname(mqtt_host) except socket.gaierror: @@ -199,14 +239,17 @@ def main(): do_watch(printer, interval=args.interval, broadcast=broadcast) sys.exit(0) - logging.info(f'Printer: {printer.describe()} ({printer.addr[0]})') + logging.info(f"Printer: {printer.describe()} ({printer.addr[0]})") if printer.busy: - logging.error(f'Printer is busy (status: {printer.current_status})') + logging.error(f"Printer is busy (status: {printer.current_status})") sys.exit(1) if args.command == "upload": - asyncio.run(do_upload(printer, args.filename, start_printing=args.start_printing)) + asyncio.run( + do_upload(printer, args.filename, start_printing=args.start_printing) + ) elif args.command == "print": asyncio.run(do_print(printer, args.filename)) + main() diff --git a/saturn_printer.py b/cassini/saturn_printer.py similarity index 100% rename from saturn_printer.py rename to cassini/saturn_printer.py diff --git a/simple_http_server.py b/cassini/simple_http_server.py similarity index 100% rename from simple_http_server.py rename to cassini/simple_http_server.py diff --git a/simple_mqtt_server.py b/cassini/simple_mqtt_server.py similarity index 100% rename from simple_mqtt_server.py rename to cassini/simple_mqtt_server.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7af2711..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -alive-progress==3.1.4 -scapy==2.5.0 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7345a0f --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup, find_packages + +setup( + name="cassini", + version="0.0.1", + install_requires=[ + "alive-progress==3.1.4", + "scapy==2.5.0", + ], + packages=find_packages(), + entry_points={"console_scripts": ["cassini = cassini.cassini:main"]}, +)