Snapmaker CLI Tool

This tool shall at some point be able to perform the following operations

  • upload a .gcode file
  • get printer status
  • get enclosure status
  • execute Marlin commands

Why Golang?

Because it's currently my favourite language.

CLI

Overview

The snap command has two ways how it gets to know the printer to talk to. The easiest, but not always reliable way is to use auto-discovery. If there is no IP address and/or token provided, snap will try to discover the printer on the network, establish an connection and retrieve an API token from the printer. The connection has to be approved in time (default 60s) on the Snapmaker controller. If more time is needed for confirmation this can be tweakd with the --confirmationTimeout argument.

If auto-discovery doesn't work, it is possible to provide an IP address as command line argument (--printer-ip). snap tries to connect to the printer and retrieve an API token. The connection has to be confirmed on the Snapmaker controller within 60s (or whatever --confirmationTimout is given).

It is possible to also provide an API token that is already known to the printer to avoid the connection confirmation using the --api-token command line argument. One could for example use the same token as Luben, which can be found in C:\Users\<username>\AppData\Roaming\snapmaker-luban\machine.json (or something similar on Linux).

General command line arguments

  • --api-token: Optional. An API token the Snapmaker already knows.
  • --printer-ip: Optional. IP address of printer, if omitted an auto-discovery will be done. TODO: auto-discovery not yet suited for multiple printers in the network.
  • --discovery-timeout: Optional. Defaults to 5s.
  • --confirmaation-timeout: Optional. Defaults to 60s.

Config file

Everytime snap establishes a confirmed connection to a printer, it stores this printers information into its config file in C:\Users\<username>\.snap.yaml. This includes IP address, API token and timeout parameters. Once a configuration is known, it will be used unless it is overriden with command line arguments.

TODO:

  • Allow multiple printers with one being selected (similar to k8s context)

Init subcommand

The init subcommand establishes an initial configuration or a renews an existing one. If --printer-ip is provided, it will try to manually to that printer otherwise auto-discovery will be used. If --api-token is provided the given API token will be reused otherwise a new one will be retrieved in which case a connection confirmation is needed.

Examples

# auto-discovery with extended confirmation timout
.\snap.exe init --confirmation-timout 120

# manual connection
.\snap.exe init --api-token aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa --printer-ip 192.168.1.2

In general the init command is not mandatory, but it is useful to reset a configuration or make sure that the connection is fine before proceeding to other endeavours.

Upload

The upload command sens a .gcode file to the printer. The full path (absolute or relatvie) to the .gcode file as to be passed as command line argument. All the general arguments regarding the connection to the printer apply.

.\snap.exe upload test.gcode

Snapmaker API

To get the upload sequence right Wireshark sniffs where used to reverse engineer the network protocol using a Snapmaker 2.0 A350.

API token

Each request is authenticated by an API token. However, the way how it's placed in the request differs for each endpoint.

TODO: It's not yet clear how this token is issued. Currently I'm using the API token used by Luban.

A token seems to be generated by Snapmaker if a connect attempt is done without a token. This token is authorized by a confirmation at the Snapmaker screen. Until the token is authorized Snapmaker answers to status and enclosure status requests with 204 - No Content. Luban is requesting the status in a loop nevertheless, which seems to be necessary to keep the confirmation screen from disappearing.

The token that used by Luban for a certain Snapmaker can be found in the following file in the section server: C:\Users\<username>\AppData\Roaming\snapmaker-luban\machine.json Using this token should work fine.

Snapmaker discovery

Discovery is done by sending an UDP packet to the broadcast address of the local network interfaces. Usually the Snapmaker then responds with a short descriptive string. The IP address of the Snapmaker can either be read from that string or from the UDP connection used for the response packet.

/api/v1/connect

As advertised, opens the connection between the tool and Snapmaker. It requires a POST request. If a token that has already been confirmed on the Snapmaker controller is known, it can be encoded as URL parameters in the body. If no token is added to the request, the Snapmaker will generate a new one and ask for a connection confirmation on the controller. Until the token is confirmed all subsequent requests will response with 204 - No Content. Nevertheless, it is important to constantly send status requests, otherwise the confirmation dialog on the controller will disappear quickly.

POST /api/v1/connect HTTP/1.1
Host: 192.168.188.130:8080
User-Agent: Go-http-client/1.1
Connection: close
Content-Length: 42
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip

token=aaaaaaaa-bbbbb-bbbb-cccc-dddddddddddd

/api/v1/status

Unsurprisingly, returns a status JSON with the current printer status. Surprisingly, it seems to be necessary to repeatedly read the printer status, otherwise the upload command will return an 401 - Unauthorized result stating that the machine is not yet connected.

The token is provided as URL parameter. Additonally to the token at timeset as Unix epoch in miliseconds is added.

http://<snapmaker>:8080/api/v1/status?token=aaaaaaaa-bbbbb-bbbb-cccc-dddddddddddd&1658951116428

/api/v1/enclosure

/api/v1/upload

Requires a multipart-form request consisting of two parts:

token part

----------------------------447327606604133343229724
Content-Disposition: form-data; name="token"

aaaaaaaa-bbbbb-bbbb-cccc-dddddddddddd

It seems to be vitally important that this part doesn't have a Content-Type header. Otherwise the upload will result in 400 - Bad request.

file part

----------------------------447327606604133343229724
Content-Disposition: form-data; name="file"; filename="test.gcode"
Content-Type: application/octet-stream

;FLAVOR:Marlin
;TIME:52383
;Filament used: 13.0944m
;Layer height: 0.08
;MINX:123.287
;MINY:140.882
;MINZ:0.15
;MAXX:196.712
;MAXY:209.939
;MAXZ:39.03
;Generated with Cura_SteamEngine 5.0.0
M82 ;absolute extrusion mode
M104 S220 ;Set Hotend Temperature
M140 S70 ;Set Bed Temperature
G28 ;home
G90 ;absolute positioning
G1 X-10 Y-10 F3000 ;Move to corner 
G1 Z0 F1800 ;Go to zero offset
M109 S220 ;Wait for Hotend Temperature
M190 S70 ;Wait for Bed Temperature
G92 E0 ;Zero set extruder position
G1 E20 F200 ;Feed filament to clear nozzle
...

/api/v1/execute_code