diff --git a/README.md b/README.md index 3c9e193..6d87f30 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ Copyright (C) 2023 Vladimir Vukicevic ## Usage -Python 3 is required. There are no other requirements, but you should -install the `alive-progress` package for nicer progress bars (`pip3 install alive-progress`). +Python 3 is required. Use `pip install -r requirements.txt` to install dependencies. ### Printer status @@ -28,6 +27,15 @@ $ ./cassini.py status File Transfer Status: 0 ``` +### Printer(s) full status + +``` +$ ./cassini.py status-full +``` + +Will print out the full json status of all printers found. + + ### Watch live print progress ``` @@ -49,6 +57,16 @@ MyFile.goo |██████████████████████ $ ./cassini.py [--printer printer_ip] print Myfile.goo ``` +### Connect printer(s) to particular MQTT server + +``` +$ [sudo] python cassini.py [--printer printer_ip] connect-mqtt mqtt.local:1883 +``` + +Probably you need to use `sudo` (at least on MacOS, or you'll get `Permission denied: could not open /dev/bpf0. Make sure to be running Scapy as root`). + +If `--printer` is not specified, all printers found will be connected to the same MQTT server. + ## Protocol Description The protocol is pretty simple. There is no encryption or any obfuscation that I could find. diff --git a/cassini.py b/cassini.py index 5794e1a..926340b 100755 --- a/cassini.py +++ b/cassini.py @@ -7,6 +7,8 @@ # License: MIT # import os +import pprint +import socket import sys import time import asyncio @@ -63,6 +65,11 @@ 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) + + 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: @@ -135,6 +142,7 @@ def main(): 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) @@ -146,6 +154,9 @@ def main(): 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() if args.debug: @@ -171,6 +182,19 @@ def main(): do_status(printers) sys.exit(0) + if args.command == "status-full": + do_status_full(printers) + sys.exit(0) + + if args.command == "connect-mqtt": + mqtt_host, mqtt_port = args.address.split(':') + try: + mqtt_host = socket.gethostbyname(mqtt_host) + except socket.gaierror: + pass + for p in printers: + p.connect_mqtt(mqtt_host, mqtt_port) + if args.command == "watch": do_watch(printer, interval=args.interval, broadcast=broadcast) sys.exit(0) diff --git a/requirements.txt b/requirements.txt index 8dfc8c8..7af2711 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ alive-progress==3.1.4 +scapy==2.5.0 \ No newline at end of file diff --git a/saturn_printer.py b/saturn_printer.py index 242af28..760c85e 100644 --- a/saturn_printer.py +++ b/saturn_printer.py @@ -14,6 +14,7 @@ import asyncio import logging import random from enum import Enum +from scapy.all import IP, UDP, send SATURN_UDP_PORT = 3000 @@ -320,3 +321,16 @@ class SaturnPrinter: } self.mqtt.publish('/sdcp/request/' + self.id, json.dumps(cmd_data)) return hexstr + + def connect_mqtt(self, mqtt_host, mqtt_port): + """ + Connect printer to MQTT server + + It spoofs UDP packet to the printer to make it connect to particular MQTT server + """ + ip = IP(dst=self.addr[0], src=mqtt_host) + any_src_port = random.randint(1024, 65535) + udp = UDP(sport=any_src_port, dport=SATURN_UDP_PORT) + payload = f"M66666 {mqtt_port}" + packet = ip / udp / payload + send(packet)