Giter VIP home page Giter VIP logo

cassini's Introduction

Cassini

ELEGOO has a number of printers, such as the Saturn 3 Ultra that support file transfer and printing over WiFi. Chitubox, Voxeldance Tango, and Lychee support this feature, but UVTools does not offer a way to print using it.

Cassini is a command-line tool to allow getting the status of your printer(s), transferring files to them, and starting a print. It has only been tested with a Saturn 3 Ultra, but may work with other ChiTu-mainboard printers with WiFi.

Note: This is still a work in progress.

License: MIT Copyright (C) 2023 Vladimir Vukicevic

Usage

Python 3 is required. Use pip install -r requirements.txt to install dependencies.

Printer status

$ ./cassini.py status
192.168.7.128: Saturn3Ultra (ELEGOO Saturn 3 Ultra)  Status: 1
          Print Status: 2 Layers: 19/130
  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

$ ./cassini.py watch [interval]
_STL_B_Warriors_1_Sword_Combined_Supported.goo |███████████████████████████████████▉ ︎   | 90% 

File transfer

$ ./cassini.py [--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)

Start a print (of an existing file)

$ ./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.

There is a UDP discovery, status, and MQTT connection protocol. When the UDP command to connect to a MQTT server is given, the printer connects to a MQTT server, and can be controlled via MQTT messages. A HTTP server is also needed for file downloads.

Note: this seems nicely set up to be able to control multiple printers from one central system. Cassini doesn't implement this, though it does allow you to choose which printer to send commands to.

(SDCP might be a standard protocol on top of MQTT, but I didn't investigate too deeply.)

UDP Status and Discovery

A UDP broadcast to port 3000 with M99999 will cause all printers to send back a JSON status blob via UDP to the sending port. This response looks like this. LASTHOST below refers to the last MQTT/SDCP server this printer was connected to.

{
  "Id": "0a69ee780fbd40d7bfb95b312250bf46",
  "Data": {
    "Attributes": {
      "Name": "Saturn3Ultra",
      "MachineName": "ELEGOO Saturn 3 Ultra",
      "ProtocolVersion": "V1.0.0",
      "FirmwareVersion": "V1.4.2",
      "Resolution": "11520x5120",
      "MainboardIP": "192.168.7.128",
      "MainboardID": "ABCD1234ABCD1234",
      "SDCPStatus": 0,
      "LocalSDCPAddress": "tcp://LASTHOST:33288",
      "SDCPAddress": "",
      "Capabilities": [
        "FILE_TRANSFER",
        "PRINT_CONTROL"
      ]
    },
    "Status": {
      "CurrentStatus": 0,
      "PreviousStatus": 1,
      "PrintInfo": {
        "Status": 16,
        "CurrentLayer": 310,
        "TotalLayer": 310,
        "CurrentTicks": 3222039,
        "TotalTicks": 3218949,
        "ErrorNumber": 0,
        "Filename": "ResinXP2-ValidationMatrix.goo"
      },
      "FileTransferInfo": {
        "Status": 0,
        "DownloadOffset": 0,
        "CheckOffset": 0,
        "FileTotalSize": 0,
        "Filename": ""
      }
    }
  }
}

Sending a UDP message to port 3000 of a specific printer with the content M66666 12345 causes the printer to make a MQTT client connection to the server that sent the message on port 12345.

The Id from this message is repeated in every MQTT message below, published by either the client or the server.

MQTT/SDCP Control

When connected via MQTT, the printer subscribes to a request topic for the printer: /sdcp/request/ABCD1234ABCD1234. The id is the MainboardID from the original discovery. Each payload

The printer publishes messages to three topics:

/sdcp/status/ABCD1234ABCD1234: Status messages, same format as the "Status" content of the broadcast message: {"Id":"f25273b12b094c5a8b9513a30ca60049","Data":{"Status":{"CurrentStatus":0,"PreviousStatus":0,"PrintInfo":{"Status":0,"CurrentLayer":0,"TotalLayer":0,"CurrentTicks":0,"TotalTicks":0,"ErrorNumber":0,"Filename":""},"FileTransferInfo":{"Status":0,"DownloadOffset":0,"CheckOffset":0,"FileTotalSize":0,"Filename":""}},"MainboardID":"ABCD1234ABCD1234","TimeStamp":8629636}}

/sdcp/attributes/ABCD1234ABCD1234: Unclear what this is used for, as it seems to repeat the Status information: {"Id":"f25273b12b094c5a8b9513a30ca60049","Data":{"Attributes":{"CurrentStatus":0,"PreviousStatus":0,"PrintInfo":{"Status":0,"CurrentLayer":0,"TotalLayer":0,"CurrentTicks":0,"TotalTicks":0,"ErrorNumber":0,"Filename":""},"FileTransferInfo":{"Status":0,"DownloadOffset":0,"CheckOffset":0,"FileTotalSize":0,"Filename":""}},"MainboardID":"ABCD1234ABCD1234","TimeStamp":8629737}}

/sdcp/response/ABCD1234ABCD1234: Responses to /sdcp/request/XXX messages. The Cmd inside Data (if present) and RequestID values will match what was sent in the request. Example: {"Id":"f25273b12b094c5a8b9513a30ca60049","Data":{"Cmd":1,"Data":{"Ack":0},"RequestID":"130fdded918e4276a47e504f554bed54","MainboardID":"ABCD1234ABCD1234","TimeStamp":8567213}}

The printer subscribes to a request topic specific to its mainboard ID /sdcp/request/ABCD1234ABCD1234, with payloads looking like:

{
    "Data": {
        "Cmd": 1,
        "Data": null,
        "From": 0,
        "MainboardID": "ABCD1234ABCD1234",
        "RequestID": "3676747651dd44b0bdbd630f38b61754",
        "TimeStamp": 1693671336726
    },
    "Id": "0a69ee780fbd40d7bfb95b312250bf46"
}

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 }

128: Start Printing

{
    "Data": {
        "Cmd": 128,
        "Data": {
            "Filename": "_ResinXP2-ValidationMatrix_v2.goo",
            "StartLayer": 0
        },
        "From": 0,
        "MainboardID": "ABCD1234ABCD1234",
        "RequestID": "b353f511680d40278c48602821a9e6ec",
        "TimeStamp": 1693671341990
    },
    "Id": "0a69ee780fbd40d7bfb95b312250bf46"
}

256: Upload file

Note: in the URL, ${ipaddr} seems to be replaced by the IP address that the printer is connected to. The file needs to be accessible at the specified URL.

{
    "Data": {
        "Cmd": 256,
        "Data": {
            "Check": 0,
            "CleanCache": 1,
            "Compress": 0,
            "FileSize": 3541068,
            "Filename": "_ResinXP2-ValidationMatrix_v2.goo",
            "MD5": "205abc8fab0762ad2b0ee1f6b63b1750",
            "URL": "http://${ipaddr}:58883/f60c0718c8144b0db48b7149d4d85390.goo"
        },
        "From": 0,
        "MainboardID": "ABCD1234ABCD1234",
        "RequestID": "5b72361a76774a96b73f091bf5f79590",
        "TimeStamp": 1693671336846
    },
    "Id": "0a69ee780fbd40d7bfb95b312250bf46"
}```

The printer will set CurrentStatus = 2 (Busy), and FileTransferInfo.Status = 0 while the transfer is in progress,
and will give Status updates. When finished, CurrentStatus will return to 0, and FileTransferInfo will be either 2 (success)
or 3 (failure).

cassini's People

Contributors

a0s avatar rorosentrater avatar vvuk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

cassini's Issues

Does this program work with the Chitu M1 board?

Hello, I'm planning to control the board remotely and have come across this project. I used Wireshark and found that it responds with 'OK' to the 'M99999' packet on port 3000, but it doesn't respond with JSON. mine uses chitu m1 and wonder if I can get any tip for this (./cassini doesn't work at all). thank you

Elegoo Mars 4 Ultra upload

File successfully transfered, but threw an exception:

PS P:\Documents\3d Printing\To Print> python C:\working\cassini-main\cassini.py upload 93762_147278_699119_char_mm.goo
13:47:57,454 INFO: Printer: Mars 4 Ultra (ELEGOO Mars 4 Ultra) (Redacted)
93762_147278_699119_char_mm.goo |████████████████████████████████████████| 100% [119561469/119561469] (4470118.45/s)
Exception ignored in: <function StreamWriter.del at 0x00000196B27B5080>
Traceback (most recent call last):
File "C:\Program Files\Python311\Lib\asyncio\streams.py", line 396, in del
self.close()
File "C:\Program Files\Python311\Lib\asyncio\streams.py", line 344, in close
return self._transport.close()
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\asyncio\proactor_events.py", line 109, in close
self._loop.call_soon(self._call_connection_lost, None)
File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 761, in call_soon
self._check_closed()
File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

Saturn 3 Ultra - Hanging after file upload.

The file upload works just fine. It is available on the printer, and I can force exit with ctrl+C and run the print command with no issue. We just get a hang with no messaging. Happens on any .goo file, any size. My attempts range from ~14MB to ~500MB. I can give the full --debug output if its needed, but for now this is the end of it, which does correctly show the transition of states:


on 168490728: 16:52:46,728 DEBUG: STATUS: {'CurrentStatus': 2, 'PreviousStatus': 0, 'PrintInfo': {'Status': 0, 'CurrentLayer': 0, 'TotalLayer': 0, 'CurrentTicks': 0, 'TotalTicks': 0, 'ErrorNumber': 0, 'Filename': ''}, 'FileTransferInfo': {'Status': 0, 'DownloadOffset': 175320953, 'CheckOffset': 0, 'FileTotalSize': 175320953, 'Filename': 'local.goo'}}
local.goo |████████████████████████████████████████| 100% [175320953/175320953] (2483410.38/s)
16:52:46,850 DEBUG: STATUS: {'CurrentStatus': 0, 'PreviousStatus': 2, 'PrintInfo': {'Status': 0, 'CurrentLayer': 0, 'TotalLayer': 0, 'CurrentTicks': 0, 'TotalTicks': 0, 'ErrorNumber': 0, 'Filename': ''}, 'FileTransferInfo': {'Status': 2, 'DownloadOffset': 175320953, 'CheckOffset': 0, 'FileTotalSize': 175320953, 'Filename': 'local.goo'}}

Afterward, console just sits until ctrl+C or other interrupt is sent, where I'm met with the following exception:

File "C:\Users\[User]\Desktop\cassini-main\cassini-main\cassini.py", line 192, in <module>
    main()
  File "C:\Users\[User]\Desktop\cassini-main\cassini-main\cassini.py", line 188, in main
    asyncio.run(do_upload(printer, args.filename, start_printing=args.start_printing))
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 193, in run
    with Runner(debug=debug, loop_factory=loop_factory) as runner:
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 62, in __exit__
    self.close()
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 70, in close
    _cancel_all_tasks(loop)
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 205, in _cancel_all_tasks
    loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 651, in run_until_complete
    self.run_forever()
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\windows_events.py", line 321, in run_forever
    super().run_forever()
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 618, in run_forever
    self._run_once()
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 1913, in _run_once
    event_list = self._selector.select(timeout)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\windows_events.py", line 444, in select
    self._poll(timeout)
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\windows_events.py", line 769, in _poll
    status = _overlapped.GetQueuedCompletionStatus(self._iocp, ms)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
16:56:10,722 ERROR: Task was destroyed but it is pending!
task: <Task cancelling name='Task-4' coro=<SimpleMQTTServer.serve_forever() running at C:\Users\[User]\Desktop\cassini-main\cassini-main\simple_mqtt_server.py:39> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[gather.<locals>._done_callback() at C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\tasks.py:757]>
Exception ignored in: <function StreamWriter.__del__ at 0x0000015A24A31A80>
Traceback (most recent call last):
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\streams.py", line 397, in __del__
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\streams.py", line 343, in close
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\proactor_events.py", line 109, in close
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 772, in call_soon
  File "C:\Users\[User]\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed

Afterward, everything is on the printer as expected, and can be run as normal.

FIX: Add READY status to saturn_printer.py

Couple of recommended tweaks:

1: No active prints return the following when "cassini.py status" is executed:

E:\Programs\Python Scripts\cassini-main>python cassini.py status
192.168.xxx.xxx:
    Saturn3Ultra (ELEGOO Saturn 3 Ultra)
    Machine Status: READY
Traceback (most recent call last):
  File "E:\Programs\Python Scripts\cassini-main\cassini.py", line 188, in <module>
    main()
  File "E:\Programs\Python Scripts\cassini-main\cassini.py", line 171, in main
    do_status(printers)
  File "E:\Programs\Python Scripts\cassini-main\cassini.py", line 61, in do_status
    print(f"    Print Status: {PrintInfoStatus(print_info['Status']).name}")
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.496.0_x64__qbz5n2kfra8p0\Lib\enum.py", line 744, in __call__
    return cls.__new__(cls, value)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.496.0_x64__qbz5n2kfra8p0\Lib\enum.py", line 1158, in __new__
    raise ve_exc
ValueError: 0 is not a valid PrintInfoStatus

The fix is to add "READY = 0" to the class PrintInfoStatus:

# Status field inside PrintInfo
class PrintInfoStatus(Enum):
    # TODO: double check these
    READY = 0
    EXPOSURE = 2 
    RETRACTING = 3
    LOWERING = 4
    COMPLETE = 16 # pretty sure this is correct

As shown below:

E:\Programs\Python Scripts\cassini-main>python cassini.py status
192.168.xxx.xxx:
    Saturn3Ultra (ELEGOO Saturn 3 Ultra)
    Machine Status: READY
    Print Status: READY
    Layers: 0/0
    File:
    File Transfer Status: IDLE

2: File Transfer Status shows ERROR when no print is active:

E:\Programs\Python Scripts\cassini-main>python cassini.py status
192.168.xxx.xxx:
    Saturn3Ultra (ELEGOO Saturn 3 Ultra)
    Machine Status: READY
    Print Status: IDLE
    Layers: 0/0
    File:
    File Transfer Status: ERROR

The fix is to rename "ERROR = 3" to "IDLE = 3" in the class FileStatus in saturn_printer.py to more accurately describe the printer's state:

# Status field inside FileTransferInfo
class FileStatus(Enum):
    NONE = 0
    DONE = 2
    IDLE = 3

As shown below:

E:\Programs\Python Scripts\cassini-main>python cassini.py status
192.168.xxx.xxx:
    Saturn3Ultra (ELEGOO Saturn 3 Ultra)
    Machine Status: READY
    Print Status: READY
    Layers: 0/0
    File:
    File Transfer Status: IDLE

Clearing the summary screen at the end of a print

Hey, this is the guy with the Mars 4 Ultra from Reddit. Figured here is a better place to provide feedback.

Currently, when the print ends, the printers has a summary of how long the print took(pic below). I assume the same happens on the Saturn 3U. This keeps the printer in status 1 and doesn't allow to upload while that summary screen is up. Is there a way to clear that screen via the protocol or do I have to manually hit 'confirm' on the printer itself?

image

Watch dont work with mars 4 ultra

silca@PC:~/cassini$ python3 cassini.py -p 192.168.1.28 status
192.168.1.28: Mars 4 Ultra (ELEGOO Mars 4 Ultra) Status: 1
Print Status: 3 Layers: 671/900 File: key_modified.ctb
File Transfer Status: 0

silca@PC:~/cassini$ python3 cassini.py -p 192.168.1.28 watch
_Duracam_key_generator_modified_3_goupilles.ctb | | ▇▇▅ 0% [0/900] (~0s, 0.0/s)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.