Skip to content

Repository files navigation

Snapmaker U1 Toolkit — Print From Your Phone with Telegram

tests

Safely slice, preview, upload, monitor, and start Snapmaker U1 prints remotely through Telegram, OrcaSlicer, Moonraker, and an optional local AI agent.

Send an STL, 3MF, or ZIP from your phone. The toolkit analyzes the model, recommends print settings, slices it locally with OrcaSlicer, generates plate previews, verifies the loaded filament and toolhead, photographs the print bed, and asks for explicit approval before starting.

Snapmaker U1 remote printing workflow from a phone through Telegram, OrcaSlicer, camera verification, and human-approved print start

What you can do

  • Print to a Snapmaker U1 from your phone through Telegram.
  • Slice STL and 3MF files with headless OrcaSlicer on Linux, WSL, or Windows.
  • Preview model orientation, plate layout, toolpaths, and important print settings.
  • Upload G-code through the U1's Moonraker/Klipper LAN interface.
  • Verify toolheads and filament before heating or motion.
  • Receive first-layer, final-layer, and completed-print camera photos.
  • Require a fresh bed-camera image and human approval before every toolkit-started print.
  • Run the deterministic CLI tools without AI, or add Hermes with a local LLM for the phone workflow.

The AI does not receive unrestricted printer control. Deterministic scripts own slicing, verification, uploading, monitoring, and the single-use print-start approval gate. See the safety model for the exact boundary.

This is how AI should touch physical machines: plan, explain, preview, ask, verify, then act only within a narrow approved boundary.

See it in action

A local model takes a ZIP of eight STLs from Telegram to a reviewed printing plate in about 160 seconds, entirely on local hardware. Nothing starts until a human approves the fresh bed photo.

STL.Upload.Flow.mp4

Start here: print from your phone · install the toolkit · set up Telegram · run headless OrcaSlicer · troubleshoot


The flow

One flow handles everything. Send a single STL or a zip of twelve — a lone model is just a kit of one, auto-detected, same entrypoint, same safety boundary:

  1. Send a model. A .stl/.3mf, or a zip of STLs (the common Printables shape). The workflow ingests every part: footprints measured, oversized parts flagged, hostile archives refused with a clean error instead of a crash.
  2. Answer one decision form — parts, print head, orientation, supports, profile. On a tool-capable model it renders as native buttons (one submit); on a small local model it falls back to a typed one-liner, or a staged one-question-per-turn flow. The form surfaces live U1 state (which filament is actually loaded on which head) and Orca's real mesh-topology verdict (floating cantilever / clean / overhang fraction) so you pick the pose Orca actually prefers. Either way a script parses and validates the answer; the model never interprets it — only an opaque form id rides through the model, and conflicting input fails loudly, never a silent guess.
  3. Arrange + slice through OrcaSlicer onto as many plates as the bed needs — T0→T<chosen> rewriting, Snapmaker thumbnail injection, real Orca warnings surfaced, and a gcode-extent guard that refuses any plate whose extrusion would leave the bed (built from a real incident, not a hypothetical).
  4. Review two corroborating previews derived from the sliced gcode (the real toolpath): a precise top-down footprint and a 3D plate view of the same per-part geometry with height added — same parts, same colors, same positions the printer will execute. Plus a review.md flight plan generated from the gcode's own config block: what will print, the ~12 settings that matter, your decisions and overrides.
  5. Upload every plate to the U1's Moonraker storage with print=false (files land; the printer does NOT start).
  6. One bed-clear decision. A fresh photo of the bed from the U1's onboard camera arrives with the previews. Reply YES to start now, or NO to keep the gcode uploaded without printing. The YES is redeemed by the gateway itself — the workflow hands the model no start command, so a misbehaving or prompt-injected agent has nothing to fire. The yes rides a single-use token bound to the plan's revision + gcode hash and to your operator identity in your private chat — if anything changed since you looked, or the reply came from anyone else, the start refuses instead of printing stale state.
  7. Gated start with a last exit. The gate re-verifies material against what's physically loaded, then opens a ~120s grace window before any command reaches the printer: tap the CANCEL button on the countdown, or reply CANCEL — both handled model-free by the gateway, so neither can be swallowed by the agent's turn. Every outcome (started, cancelled, or refused-with-reason) is messaged to you by the machinery, never narrated by the model. Plate 1 is the only toolkit-started plate; plates 2..N start from the Snapmaker app — the watchdog photographs every plate either way.
  8. Monitoring takes over — first-layer photo, last-layer check, completion (see Always-on print monitoring).

Steps 1–5 are useful as CLI utilities even if you never touch an AI agent. Steps 6–8 are where the operator gate makes the difference between "AI presses print" and "AI safely shows you the print so you can press it."

Also in the flow

The same entrypoint and the same bed-clear gate handle three more moves:

  • Reprint. Say "reprint" (no file needed) and pick from your recent jobs. The gcode already on the printer is reused — no re-slicing — the original previews and review doc come back with a fresh bed photo, and the normal gate runs with the same drift and identity checks.
  • Quantity. On a single part, ask for 1–9 copies; they pack onto the plate through the same arranger and split across plates when the bed fills.
  • Advanced settings. An optional screen off the review card exposes infill density and pattern, wall count, brim, fuzzy skin, and tree-vs-grid supports — each defaulting to the profile's own value, each verified into the sliced gcode.

What This Is Not

  • It is not an autonomous printer driver. No agent in this stack can start a print without an explicit operator yes/no. Normal starts are approved against a fresh U1 camera photo captured in-the-moment; if the camera is unavailable, the only alternative is an explicit, audited manual bed-verification path (never a silent skip).
  • It is not a generic slicer wrapper. Specific profile resolution, T0→T rewriting, Snapmaker thumbnail injection, and Moonraker storage discipline are baked in for the U1.
  • It is not a multi-printer abstraction yet. The safety model and event contract are portable in principle. The implementation is U1-specific by design until the U1 experience is solid.
  • It is not a Hermes-only project. Hermes is the convenient remote-control layer. Every workflow step has a CLI form and JSON event stream — wrap it with whatever you want.

Safety Model

Hermes — and any other AI agent layered on top — can recommend, explain, and prepare a print, but the U1 toolkit owns the final safety checks and will not perform printer-affecting actions without an explicit operator approval tied to a specific request ID.

The model is never given a way to start a print. The workflow hands the agent no start command at all — when you reply YES at the bed-clear prompt, the gateway itself redeems it and runs the start gate; the agent runs nothing. That YES is single-use and bound to your operator identity in your private chat, so a wrong sender, a wrong conversation, or a stale plan all refuse. The agent's only unattended power over the printer is in the safe direction: it can help you cancel a pending start, never begin one. This boundary was not designed on paper — it was forced by a live incident where an earlier build let the model fire a start it had been handed, then hardened across several rounds of adversarial review. In the default single-host deployment a deliberately hostile agent is contained (every start is audited, gated, and cancellable) rather than cryptographically prevented; the separate-user boundary that would make it prevention is on the roadmap. The full boundary, and that honest limit, are documented in docs/SAFETY.md.

The default lifecycle:

read state → slice → preview → upload-only → operator approval → start → monitor

Actions that always require explicit operator confirmation:

  • Starting a print
  • Resuming or canceling a print
  • Heating nozzle or bed
  • Moving axes
  • Clearing alarms
  • Changing tool state
  • Anything that affects the physical printer

The workflow fails closed. If a check is unsure, it stops and asks rather than guessing. Bed-clear verdicts come from the operator looking at a real photo, not from the toolkit deciding the bed is "probably fine." Slicer profile mismatches abort BEFORE the slice. Upload that hits a filename collision asks before overwriting.

What the operator approves

Every operator decision is concrete and tied to a specific artifact:

Decision What the operator sees
Parts / orientation Parts thumbnail grid + Orca's mesh-topology verdict for the recommended pose
Tool / filament Live U1 toolhead state ("T0: Generic white PETG (loaded)")
Preset Recommended profile based on model class + your print history
Supports Overhang verdict from a fast draft slice — Orca's real call, not face-angle
Pre-print review A review.md flight plan generated from the sliced gcode's own config block — bound to the plan's revision + hash so what you read is what prints
Bed clear A real, fresh photo of the bed from the U1's onboard camera. One yes/no. Default is no.

If anything is unknown — printer state, tool, material, slicer metadata, bed visibility — the workflow stops and asks. No silent assumptions.

What the exchange actually looks like after the form is submitted:

Bot: (plate preview, 3D view, review.md, and a fresh bed photo arrive) Sliced plate, review doc, and a fresh bed photo are attached. Bed clear and ready to print? Reply YES to start now, or NO to keep the gcode uploaded without printing.

You: yes

Bot: ⚠️ Snapmaker U1 print starting in 120s. Reply CANCEL to abort. Ignore this to let the print start.

If a re-slice or plan change happened between the photo and the "yes," the start refuses and re-asks with the new revision instead of starting on stale state.

The full per-action breakdown (the test-operator fence, the grace-period cancel chain, and every allowed-vs-gated command) lives in docs/SAFETY.md.

None of this is aspirational: the full test suite runs in CI on every change (the deployed runtime is Python 3.13), and the cancel chain is live-verified on real hardware, including a reproducible, no-printer-needed drill anyone can run: docs/verify-cancel-hook.md.

Always-on print monitoring — no agent required

The start decision needs an LLM + your explicit approval. Watching the print once it's running does not — that part is three quiet cron jobs, no agent turn, no LLM in the loop at all.

Why first-layer specifically matters: it's the earliest real tell a print is about to fail — bed adhesion problems, warping, a shifted part, wrong Z-offset all show up in the first few layers, long before you'd otherwise notice. A last-layer photo confirms it finished; a first-layer photo is the one that could actually save you hours of wasted filament and time if you catch it early.

Job Cadence What it does
u1_last_layer_watch.py every 1 min Snaps a photo at first-layer (layers 2–5, the bed-adhesion check), at last-layer (final ~6 layers), and after a pause/resume (an extra confidence check). Delivers each straight to Telegram. Auto-dims the cavity LED a few minutes after the job completes/errors/cancels.
u1_print_watchdog.py every 5 min Silent health poll across any active U1 print, not just Hermes-started ones. Alerts once per distinct issue, with cooldown so it never spams.
u1_print_history.py every 5 min Appends to a durable print ledger.

All three run in Hermes' no_agent cron mode — a plain script invocation with no persona and no model call, so there's nothing for a weak or a strong model to get wrong, and nothing that can fabricate a milestone that didn't happen (the photo either exists or the job says nothing). They watch every active print, whether it was started through this toolkit, the Snapmaker app, or anything else touching the same Moonraker.

The Three Layers

The toolkit ships as three layers that build on each other. Pick your mode, then install below.

1. CLI mode — useful without Hermes

Scriptable, deterministic, single-purpose tools that a U1 owner can use directly:

  • Slice + preview a model
  • Inspect printer state, profiles, print history
  • Generate orientation renders
  • Upload a job with print=false
  • Review G-code metadata before printing

These are designed for shell scripts, cron jobs, manual workflows. No AI required.

2. Operator workflow — the staged experience

A multi-step state machine that walks an operator through the print decision. Emits structured JSON events at every step, so any frontend (Telegram bot, web UI, custom integration) can wrap it without re-implementing the logic.

This is the core product. It's what makes the toolkit feel like a responsible assistant instead of a generic API wrapper.

3. Hermes mode — the remote-control layer

A bundled Hermes skill (3d-printer-slicing-automation) that lets a Telegram-bridged Hermes agent drive the operator workflow on the user's behalf. The agent:

  • Surfaces the workflow's questions and previews to the user verbatim
  • Tool-calls the named scripts (never invents its own slicing path)
  • Never decides bed-clear status on its own — the operator does, looking at a real photo

The skill is designed to work on small local models (gemma4-26b-64k and below) via Ollama. See Hermes integration for the full setup.

Install

One install path. Everything below assumes a U1 reachable on your LAN and a Linux (or WSL) host. Native Windows is experimental — see docs/WINDOWS.md for what differs and how to validate it.

Requirements: Python 3.9+, numpy + Pillow (via requirements.txt), an OrcaSlicer 2.4.0+ CLI binary (extracted AppImage is fine — full steps in Headless slicing setup), and network reachability to your U1's Moonraker port (default 7125).

git clone https://github.com/bbolinger/snapmaker-u1-toolkit.git
cd snapmaker-u1-toolkit
python3 -m pip install -r requirements.txt

# Point the toolkit at your printer (.env is auto-loaded on first config read)
cp .env.example .env       # edit: set SNAPMAKER_U1_HOST to your U1's LAN IP

# Fetch Snapmaker's stock U1 profiles (~217 files) + extract your own history
python3 tools/fetch_snapmaker_profiles.py
python3 tools/extract_profiles_from_printer.py   # optional but recommended

# Verify (argparse usage text = your environment is ready)
python3 scripts/u1_slice_workflow.py --help

# Read-only status probe (no risk)
python3 scripts/snapmaker_u1_status.py

The networked steps (extract_profiles_from_printer.py, snapmaker_u1_status.py) time out until SNAPMAKER_U1_HOST points at your printer's real LAN IP. The .env.example default 192.168.1.100 is only a placeholder, so edit .env first, or skip those two until the printer is reachable.

On Windows (PowerShell) the same steps apply with Copy-Item .env.example .env and backslash paths; the data dir defaults to C:\Users\<you>\.local\share\snapmaker-u1 (override with $env:SNAPMAKER_U1_DATA_DIR).

Choosing a Python interpreter. The workflow needs numpy and Pillow on the Python that runs it and auto-detects a working interpreter (first that can import numpy, PIL wins): $U1_TOOLKIT_PYTHON, then /opt/hermes/.venv/bin/python, then a project-local venv/.venv, then the Homebrew paths. If none has the deps, it exits listing every path it tried and how to fix it. Cleanest isolated setup:

python3 -m venv venv
venv/bin/pip install -r requirements.txt
export U1_TOOLKIT_PYTHON=$PWD/venv/bin/python   # add to your shell rc to persist

Connection, data-dir, and LED behavior are covered in Configuration. If something fails, check TROUBLESHOOTING.md.

First slice

Workflow preview render — auto-oriented mounting plate flat on bed, U-cradle upright; first-layer footprint parsed from real Orca G-code

The canonical entry point for a model or a kit zip (kit-of-one auto-detected):

python3 scripts/u1_slice_workflow.py model.3mf

Agent/Telegram wrappers should consume the event stream instead of re-implementing the workflow:

python3 scripts/u1_slice_workflow.py model.3mf --json-events

Safe headless proof run — decisions passed as flags, upload-only, no printer start (list your profile slugs with python3 scripts/u1_profile_picker.py):

python3 scripts/u1_slice_workflow.py model.3mf \
  --tool T1 --material PETG --orient auto \
  --profile 0_20_strength_snapmaker_u1_0_4_nozzle \
  --supports auto --upload-only --yes

Without profiles the workflow exits with a setup_required event and points you back at the fetch/extract tools from Install.

For the design rationale, architecture, and acceptance criteria, see docs/DESIGN-CONTRACT.md. For the public event contract (every event the workflow + audit log emit, with payload shapes), see docs/events.md.

Hermes integration

Install on Linux. The deploy scripts are bash and install.py targets a Linux venv (bin/, lib/pythonX.Y/). Run it inside the Hermes container, on a Linux host, or under WSL / Git Bash on Windows — not raw Windows cmd. Hermes typically ships numpy + Pillow in its bundled venv (verify: /opt/hermes/.venv/bin/python -c 'import numpy, PIL; print("ok")').

Run each step and let it finish before the next. Step 1 asks a y/N you have to answer, so do not paste the whole block at once.

1. Install the bundled skill (answer y at the prompt):

hermes skills install bbolinger/snapmaker-u1-toolkit/skills/3d-printer-slicing-automation

2. Deploy the workflow scripts to the runtime paths the skill calls into:

bash deploy_to_runtime.sh

3. Install both Hermes plugins and patch the gateway. This installs the u1-form tool plugin and the snapmaker_u1 hook plugin (auto-skill load, next-action guard, and the image / review-doc attachment injector). Add --venv <path> if your Hermes venv is not /opt/hermes/.venv:

python3 adapters/hermes/install.py

install.py loads two plugins: u1-form and snapmaker_u1 (a pip entry point, installed editable so git pull updates it). Without this step the form, the auto-skill trigger, and the image/review-doc attachments do not load. Watch for its [4/6] install the snapmaker_u1 hook plugin and a [6/6] verify ending OK: hooks=...transform_llm_output.

4. Install both gateway hooks (the operator YES that starts a print and the reply/tap CANCEL that stops one), restart the gateway, and verify:

bash tools/install_hermes_u1_hooks.sh
hermes gateway restart
bash tools/install_hermes_u1_hooks.sh --verify

If you run these from inside a Hermes Desktop or gateway chat, run hermes gateway restart from a separate terminal outside Hermes. The gateway refuses to restart itself from within its own process (it would kill the command mid-run), so the hooks stay unloaded until you restart it externally.

The YES/CANCEL hooks bind to one operator in one private Telegram DM. With a single user id in TELEGRAM_ALLOWED_USERS the binding resolves itself; otherwise set it explicitly in the runtime .env:

U1_OPERATOR_BINDING=telegram:<your-numeric-telegram-user-id>

Without the hooks installed, a YES at the bed-clear prompt does nothing (fail-safe: the printer never starts) — --verify and the deploy script both tell you loudly.

The deploy script verifies the deployed workflow actually starts (✓ workflow starts cleanly); override target paths via U1_DEPLOY_SCRIPTS / U1_DEPLOY_TOOLS / U1_DEPLOY_SKILL / U1_DEPLOY_PROFILES if your layout differs from the Hermes default.

End-to-end example with Hermes — model preview, Telegram operator conversation, AI-derived slice settings, and the actual printed part in hand

The skill tells Hermes to call scripts/u1_slice_workflow.py, follow the workflow's events, default to upload-only, and fail closed at the bed-clear start gate.

Local model & serving requirements (form mode / button UX)

The button-based form mode (the rich Telegram UX in the demo) asks the local model to emit one tool call. Small local models are inconsistent at tool-calling, so form mode has hard serving requirements — verified end-to-end on gemma4-26b-64k via Ollama on an NVIDIA Quadro P6000:

  • Ollama 0.31.1 or newer. Ollama 0.30.x has a gemma4 tool-call parser bug (#15539, #15798, #15943): the model's tool call leaks into the message content as raw template tokens (<|channel|>, <|"|>), the parser misses it, finish_reason is stop, and the agent stalls with no buttons.

  • Run the model at low temperature (~0.2) for tool turns. Gemma's default Modelfile ships temperature 1, which is unreliable for tool calls (~2 of 3 succeed in testing — one run in three strands the operator). A temp-0.2 variant is 3 of 3. Create one (it shares the same weights blob — no extra disk):

    printf 'FROM gemma4-26b-64k:latest\nPARAMETER temperature 0.2\nPARAMETER num_ctx 65536\n' \
      | ollama create gemma4-26b-64k-tool -f -

    then point your agent's model at gemma4-26b-64k-tool.

  • The toolkit already does its part. The kit_form event carries only a short form_id; the full form definition is persisted to disk and loaded by the form plugin, so the model never has to reproduce a large nested schema in its tool call (what small models fail at). Nothing to configure — just don't downgrade the bundled u1-form plugin.

All three are needed together: on 0.30.8 the model failed even with the flat form_id call; on 0.31.1 the flat call works, but only at low temperature is it reliable. If tool calls still fail, fall back to text mode (--interaction-mode text) — the staged one-question-per-turn flow uses only simple terminal calls that even small models handle reliably.

Hardware note (Pascal / older GPUs). Ollama 0.31's cuda_v13 runtime dropped Pascal (compute capability 6.1); it falls back to the bundled cuda_v12, so a P6000 / GTX-10-series still works today — but a future Ollama that drops cuda_v12 would break it. Benign driverInitFileInfo ... result=11 lines at startup are that fallback, not a failure.

Gotcha for skill writers: Hermes attaches files via bare paths in text, not a tool parameter

If you fork this skill or write your own, Hermes' platform gateways (Telegram, Discord, Signal, etc.) deliver media to the user by scanning the agent's reply text for bare absolute file paths ending in known media extensions and auto-attaching whatever exists on disk. There is no files=[...] tool parameter the agent needs to call. See gateway/platforms/base.py:extract_local_files() in Hermes 0.15.2 for the canonical implementation.

What this means for your skill prompt:

  • ✅ Tell the agent: "emit the absolute path bare in your reply text"
  • ❌ Do NOT tell the agent: "attach the file via the reply tool's files parameter"
  • ❌ Paths inside backticks or fenced code blocks are skipped — the agent must emit them as bare text

This caught me out during the first live test — the agent kept claiming it would "attach" renders but the gateway saw nothing to extract. See TROUBLESHOOTING.md for the full diagnosis if you hit the same.

What's in here

Script What it does
u1_config.py Centralized host/port resolution (env > JSON > default)
u1_camera.py Camera capture via Snapmaker-specific websocket camera.start_monitor; auto-on/restore the cavity LED for each capture via u1_led.photo_wrap
u1_led.py Cavity LED helper — CLI (status / on / off / set --r/g/b/w / is-on) and photo_wrap() context manager. The U1's cavity LED is white-only (white_pin: PA10 in printer.cfg); the 4-channel API matches Klipper's interface, only WHITE has visible effect
u1_toolmap.py Multi-tool material gate — declared vs detected material check
u1_preflight.py Combined Moonraker state + camera freshness packet for "is it safe to start?"
u1_upload_gcode.py Upload-only (print_started=false) with gates: idle state + tool/material match
u1_slice_workflow.py Canonical end-to-end STL/3MF entry point: orient → render → slice → preview → upload-only/start gate
u1_kit_workflow.py The unified kit workflow behind it — ingest, one decision form, arrange, slice, previews, bed-clear gate
u1_last_layer_watch.py Watch active print for first-layer (2–5) and "last ~6 layers" milestones, snap photos; also auto-dims the cavity LED 5 minutes after complete/error/cancelled (U1_LED_OFF_DELAY_SEC overrides)
u1_print_watchdog.py Quiet cron-driven health watcher with per-issue cooldown to avoid notification spam
u1_print_history.py Append-only JSONL print ledger + canonical upserted JSON
snapmaker_u1_status.py Read-only status probe
snapmaker_u1_snapshot.py Websocket camera trigger helper
tools/extract_profile_from_gcode.py One-shot extractor — turn a successful G-code into Snapmaker Orca process + filament JSONs
tools/extract_profiles_from_printer.py Auto-pull recent G-codes off your U1 over Moonraker, run the extractor against each — one command, gets your real print history into profiles/from-printer/
tools/fetch_snapmaker_profiles.py Fetch Snapmaker's official U1 stock profiles (machine + process + filament) from the upstream Snapmaker/OrcaSlicer GitHub repo into profiles/snapmaker-stock/
tools/gcode_inject_thumbnail.py Add Snapmaker-app preview thumbnails to headless-sliced G-code (PIL renderer + base64 splice)
tools/render_stl_orientation.py Pre-print orientation review — 4-view PNG (isometric, front, side, top) with overhang faces highlighted in orange

Configuration

u1_config.py resolves two things — the connection to the printer, and the data dir where runtime state lives (configs, photos, ledgers).

Connection (host/port)

  1. Environment variables: SNAPMAKER_U1_HOST, SNAPMAKER_U1_PORT
  2. JSON file: location from SNAPMAKER_U1_CONFIG env, default <data-dir>/u1_config.json (contains {"host": "...", "port": 7125})
  3. Hardcoded default: port 7125 only — host is required

Data dir (where runtime artifacts live)

  1. SNAPMAKER_U1_DATA_DIR env var (explicit override)
  2. /opt/data/snapmaker_u1 if it exists (auto-detects Hermes-style installs — for the agent setup these scripts came from)
  3. ~/.local/share/snapmaker-u1 (community default, follows XDG Base Dir)

All host/port/data-dir lookups happen on first call — import u1_toolmap (or any other script) never touches disk for config. The lookup only fails when you actually run a command without any configuration.

See .env.example for a starting template.

Cavity LED auto-control

The U1's cavity_led is white-only — Snapmaker's shipped printer.cfg defines it as [led cavity_led] / white_pin: PA10, no R/G/B. Klipper's [led] interface exposes all four channels regardless, but only the W channel is physically wired. The toolkit drives the LED in two places so the operator doesn't have to think about it:

  • Every camera capture (u1_camera.py photo, and therefore every milestone photo from u1_last_layer_watch.py) is wrapped in a u1_led.photo_wrap() context manager:
    • LED already on → no change, no flicker.
    • LED off → turn on white (W=1), settle ~300 ms for the camera's auto-exposure, capture, then restore the LED to off.
  • 5 minutes after a print finishes (print_state enters complete, error, or cancelled) the LED is turned off, once per print. If you manually turn it back on, it stays on — the watcher dedups by job_key = filename|total_layer and won't re-fire for the same print.

Tuning / disabling:

  • U1_LED_OFF_DELAY_SEC=N env var — grace window before auto-off. Default 300. Set 0 for immediate. Set a large value (e.g. 86400) to effectively disable the auto-off without removing the wiring.
  • The wiring is fail-soft: if cavity_led isn't configured on your printer (or the LED endpoint errors), the LED helper logs to stderr and the photo/watcher keeps doing its primary job.
  • Manual control via the CLI: u1_led.py status / on / off / set --r --g --b --w.

Why: photos taken at first/last-layer/post-resume milestones need the LED on to be useful, but leaving the cavity bright forever after a finished print is wasteful and surprising. The 5-minute grace gives you time to inspect the bed before it goes dark.

Profile sources

Your profiles should be yours: extracted from your own successful prints, mapped to your own extruders. The toolkit ships with an empty profiles/ dir on purpose and gives you three honest sources: Snapmaker's stock profiles, profiles extracted from prints that already succeeded on your printer, and your own hand-tuned files. The full guide (first-run setup, supports auto-detection, and building per-extruder profiles from print history) is in docs/PROFILES.md.

Headless slicing setup (no GUI / scripted)

Slicing from the CLI in a container, CI pipeline, or agent workflow, with no OrcaSlicer GUI: which build to use, the 3-profile load order, a real inheritance pitfall and its workaround, and the orientation-review and thumbnail-injection tools. Full guide: docs/HEADLESS.md.

Optional: notify me when OrcaSlicer has an update

The toolkit ships a small checker that compares your installed orca-slicer version against the upstream latest release. It does nothing unless you wire it into your scheduler. Cloning the repo does not subscribe you to anything.

To enable, add one line to cron (Linux/macOS):

0 7 * * * /usr/bin/python3 /path/to/snapmaker-u1-toolkit/tools/check_for_updates.py

Behavior:

  • Silent when you're current. No stdout → no cron email.
  • Single-line stdout when an update is available — cron mails it via your usual cron-email setup. Example: OrcaSlicer 2.4.1 available (you have 2.4.0). Patch (bug fixes, likely safe). Release notes: https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/v2.4.1
  • Refuses to query GitHub more than once per 24h regardless of how often you invoke it (cache at ~/.cache/snapmaker-u1-toolkit/update-check.json). --force overrides for one-off "tell me now" runs.
  • Returns silently when GitHub is unreachable or the binary isn't present. Never breaks your cron with stray stderr.

Compatibility note: Snapmaker upstreamed the U1 vendor profile into OrcaSlicer 2.4.0, and tools/fetch_snapmaker_profiles.py pulls fresh stock profiles from that upstream — patch/minor upgrades should keep slicing U1 prints. Major-version bumps may change CLI flags or profile schema — re-run the EGO trimmer regression after upgrading. The notifier's risk label ("patch / minor / major") flags this in the alert text.

If your orca-slicer binary lives anywhere other than /opt/data/tools/orcaslicer/squashfs-root/bin/orca-slicer (Hermes-container default), pass the path explicitly OR set the ORCA_SLICER_BIN environment variable in your crontab, otherwise the script silently can't probe your installed version and you'll never see notifications.

CLI:

python3 tools/check_for_updates.py                                    # daily-cached check
python3 tools/check_for_updates.py --force                            # bypass cache, hit GitHub now
python3 tools/check_for_updates.py --orca-bin /path/to/orca-slicer    # one-off
ORCA_SLICER_BIN=/path/to/orca-slicer python3 tools/check_for_updates.py  # persistent env

Reference docs

Real reverse-engineering notes from getting these scripts working — the kind of stuff Snapmaker doesn't document publicly:

Doc Topic
references/snapmaker-u1-lan-probe-2026-06.md Open ports, working endpoints, API key handling
references/snapmaker-u1-camera-websocket.md Snapmaker-specific camera.start_monitor websocket method
references/snapmaker-u1-headless-slicing-and-toolhead-notes-2026-06.md OrcaSlicer CLI for headless slicing, tool naming gotchas
references/snapmaker-u1-filament-detection-api-2026-06.md Filament presence/material detection objects
references/snapmaker-u1-printhead-numbering-and-start-workflow-2026-06.md T0..T3 mapping to extruder..extruder3
references/snapmaker-u1-last-layer-photo-watch-2026-06-21.md Last-layer event detection for milestone photos
references/snapmaker-u1-toolmap-script-2026-06.md Material-gate design rationale
references/snapmaker-u1-orca-moonraker.md OrcaSlicer + Moonraker integration
references/snapmaker-u1-research.md First-pass research summary

Running the tests

python3 -m venv .venv
source .venv/bin/activate
pip install pytest
pip install Pillow numpy   # only needed for the thumbnail-injector tests
pytest -v

The suite (CI runs it on every pull request; the deployed runtime is Python 3.13) covers: config resolution (incl. 3-tier data-dir, .env auto-loader with quoted/commented/walk-up edge cases, import-without-config regression lock, and a smoke-runner that exercises every script's main() to catch leftover undefined refs), material gate (incl. fail-closed on corrupt map), upload pre-checks, G-code metadata parsing, print-history ledger (incl. atomic-write contract + tmpfile cleanup on failure), profile extraction (incl. multi-tool slice handling — PETG;PETG;PLA;PLA → right value for the actual tool), thumbnail injection, upload-time thumbnail wiring, status-probe safe_to_upload parity with the actual upload gate, preflight --host override correctness, STL parsing + view rotations + overhang detection + 4-view orientation sheet rendering, bundled machine-profile completeness (standalone, klipper gcode flavor, 4 extruders, required slicing fields), printer-side profile extraction (Moonraker list + download mocked, friendly errors).

Tests use mocked Moonraker responses — no real printer required. The thumbnail-injection tests importorskip PIL/numpy, so they're harmless to omit if you only want to run the safety-script tests.

Release validation

Each tagged release is validated before publish: the full test suite in CI (on the deployed Python 3.13 runtime), a fresh-clone install and script-help smoke test, the active-print upload-gate safety check against a mocked Moonraker, and, for releases that touch the safety boundary or the operator flow, a live run on a real U1. Per-release evidence lives in the CHANGELOG.

Known limitations / design notes

  1. Single-printer scope: scripts assume one U1. Multi-printer would need namespacing in the config + per-printer state dirs.
  2. Cron / always-on cadence: the watchdog, last-layer, and history scripts are written to be cron-driven (the table above shows the defaults; the intervals are yours to set). They keep state on disk and are idempotent across runs, but they're not daemonized; your scheduler (cron, systemd timer, Hermes' cron, etc.) owns the cadence.
  3. U1 firmware coupling: tested against Snapmaker U1 firmware on the version that ships Moonraker on port 7125. Other Snapmaker models, or future firmware revisions that change the print_task_config / filament_detect object shape, may surface field gaps. The references/ docs capture what the current firmware does emit — start there if you're debugging a field-shape mismatch.

License

MIT — see LICENSE.

Contributing

PRs welcome, especially:

  • Additional reference docs as new firmware behaviors are reverse-engineered
  • Material gate enhancements (multi-tool prints, prime-tower extruder assignment)
  • Multi-printer support (namespacing config + per-printer state dirs)

Please run pytest before submitting — all tests should pass. See CONTRIBUTING.md for setup, conventions, and the safety-model rules that PRs need to respect.

Acknowledgments

These scripts were developed and validated against a real Snapmaker U1 over weeks of daily use. Real prints span single models and multi-part kits, generic and brand-specific PETG plus PLA, supports and no-supports, and run times from ~25 minutes to ~12 hours. The safety gate has both blocked a real material mismatch before any heat and carried full prints to completion, driven live over Telegram by a local model.

Philosophy: profiles should be YOUR profiles, extracted from YOUR successful prints, mapped to YOUR extruders. The included profiles/ directory shows the shape of those files but is environment-specific. The toolmap gate enforces per-extruder material assignment so wrong-slot mistakes don't waste filament.

Bug reports and PRs from other U1 owners welcome — especially the profile-extraction methodology being tried on different setups (smooth PEI, glass beds, other PETG brands, multi-tool configurations).

About

Print to your Snapmaker U1 from your phone: Telegram, headless OrcaSlicer, Moonraker upload, camera verification, and a human-approved start gate.

Topics

Resources

Contributing

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages