feat: add connect-mqtt command
This commit is contained in:
22
README.md
22
README.md
@@ -16,8 +16,7 @@ Copyright (C) 2023 Vladimir Vukicevic
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Python 3 is required. There are no other requirements, but you should
|
Python 3 is required. Use `pip install -r requirements.txt` to install dependencies.
|
||||||
install the `alive-progress` package for nicer progress bars (`pip3 install alive-progress`).
|
|
||||||
|
|
||||||
### Printer status
|
### Printer status
|
||||||
|
|
||||||
@@ -28,6 +27,15 @@ $ ./cassini.py status
|
|||||||
File Transfer Status: 0
|
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
|
### Watch live print progress
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -49,6 +57,16 @@ MyFile.goo |██████████████████████
|
|||||||
$ ./cassini.py [--printer printer_ip] print 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
|
## Protocol Description
|
||||||
|
|
||||||
The protocol is pretty simple. There is no encryption or any obfuscation that I could find.
|
The protocol is pretty simple. There is no encryption or any obfuscation that I could find.
|
||||||
|
24
cassini.py
24
cassini.py
@@ -7,6 +7,8 @@
|
|||||||
# License: MIT
|
# License: MIT
|
||||||
#
|
#
|
||||||
import os
|
import os
|
||||||
|
import pprint
|
||||||
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -63,6 +65,11 @@ def do_status(printers):
|
|||||||
print(f" File: {print_info['Filename']}")
|
print(f" File: {print_info['Filename']}")
|
||||||
print(f" File Transfer Status: {FileStatus(file_info['Status']).name}")
|
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):
|
def do_watch(printer, interval=5, broadcast=None):
|
||||||
status = printer.status()
|
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:
|
||||||
@@ -135,6 +142,7 @@ def main():
|
|||||||
subparsers = parser.add_subparsers(title="commands", dest="command", required=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 = 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 = 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_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 = subparsers.add_parser('print', help='Start printing a file already present on the printer')
|
||||||
parser_print.add_argument('filename', help='File to print')
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
@@ -171,6 +182,19 @@ def main():
|
|||||||
do_status(printers)
|
do_status(printers)
|
||||||
sys.exit(0)
|
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":
|
if args.command == "watch":
|
||||||
do_watch(printer, interval=args.interval, broadcast=broadcast)
|
do_watch(printer, interval=args.interval, broadcast=broadcast)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@@ -1 +1,2 @@
|
|||||||
alive-progress==3.1.4
|
alive-progress==3.1.4
|
||||||
|
scapy==2.5.0
|
@@ -14,6 +14,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from scapy.all import IP, UDP, send
|
||||||
|
|
||||||
SATURN_UDP_PORT = 3000
|
SATURN_UDP_PORT = 3000
|
||||||
|
|
||||||
@@ -320,3 +321,16 @@ class SaturnPrinter:
|
|||||||
}
|
}
|
||||||
self.mqtt.publish('/sdcp/request/' + self.id, json.dumps(cmd_data))
|
self.mqtt.publish('/sdcp/request/' + self.id, json.dumps(cmd_data))
|
||||||
return hexstr
|
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)
|
||||||
|
Reference in New Issue
Block a user