# Troubleshooting The common failure modes and how to diagnose them, in roughly the order people hit them. v0.2.x-specific fixes that landed long ago are no longer listed here - see [CHANGELOG.md](CHANGELOG.md) for the bug-fix history. ## Tile shows "Connecting…" longer than ~10 seconds The app gives every printer up to one full poll cycle (4 s) before falling back to the tunnel, then up to another 8 s for the tunnel attempt. If a tile stays on "Connecting…" longer than ~15 s on a fresh launch: - Check Moonraker is running on the Pi: ```bash sudo systemctl status moonraker ``` - Check the Moongate plugin loaded: ```bash grep -i moongate ~/printer_data/logs/moonraker.log | tail -20 ``` - Check the auth proxy is running (v0.4+): ```bash sudo systemctl status moongate-authproxy ``` - Check the tunnel service: ```bash sudo systemctl status moongate-tunnel ``` All four (`moonraker`, `klipper`, `moongate-authproxy`, `moongate-tunnel`) need to be `active (running)` for the tile to flip from "Connecting…" to a live status. ## KlipperScreen: "Cannot connect to Moonraker - Connection refused" Moongate binds Moonraker to `127.0.0.1` (localhost) during install, so the only thing exposed on your network is the EdDSA-gated tunnel proxy - not Moonraker itself. Any client **on the Pi** that reaches Moonraker by its **LAN IP** (instead of localhost) therefore stops working and shows `[Errno 111] Connection refused`. KlipperScreen is the usual one to break, since it's often configured with the Pi's IP. Point it at `127.0.0.1`. Edit `~/printer_data/config/KlipperScreen.conf`, find the `[printer ]` section, and set the Moonraker host to localhost: ```ini [printer My Printer] moonraker_host: 127.0.0.1 moonraker_port: 7125 ``` This lives under `[printer …]`, **not** `[server]` - KlipperScreen has no `[server]` section and rejects the file with *"Section [server] not recognized"* if you add one. If there's no `moonraker_host` line, add it under the existing `[printer …]` header. Then restart KlipperScreen - over SSH: ```bash sudo systemctl restart KlipperScreen ``` …or restart it from the **Services** panel in Mainsail / Fluidd (no SSH needed). `127.0.0.1` is also sturdier than a LAN IP - it survives the Pi's address changing on a DHCP renewal. A client on a **separate device** (a standalone KlipperScreen tablet, a second Pi, a central Mainsail server) can't use localhost - point it at the Pi's **port 80** instead. See the next section. ## A central (farm) Mainsail or Fluidd can no longer reach the printer on port 7125 Same cause as the KlipperScreen entry above, seen from another machine: Moongate's tunnel-mode install binds Moonraker to `127.0.0.1`, so anything that used to talk to `http://:7125` directly now gets `ERR_CONNECTION_REFUSED`. A centrally hosted Mainsail or Fluidd (one web UI managing several printers, each added by IP) is the usual victim, because its printer entries point at `:7125`. The fix is one edit in the central UI, not on the Pi: change that printer's entry from `:7125` to `` (**port 80**). The Pi's own nginx - the thing already serving Mainsail at `http:///` - proxies the full Moonraker API and websocket on port 80. That is exactly how the Pi's local web UI and the Moongate app's LAN mode connect, and it is why "connecting directly to the printer's IP works" even while `:7125` refuses. CORS and trusted-client checks behave the same through nginx, so an entry that worked on 7125 before Moongate works on 80 after. The same trick covers most separate-device clients that let you set a host and port - a standalone KlipperScreen tablet, for example, with `moonraker_host: ` and `moonraker_port: 80`. If some tool genuinely needs raw `:7125` back, you can undo the hardening: edit `~/printer_data/config/moonraker.conf`, set `host: 0.0.0.0` under `[server]`, and restart Moonraker. Remote access through Moongate keeps working (the tunnel reaches Moonraker over localhost either way), and plugin updates never re-apply the bind - only a full re-run of the installer in tunnel mode would. The trade-off is Moonraker listening on your LAN again, which is the stock Klipper-install posture. ## Updating Moongate from KlipperScreen looks stuck on "create mode 100644" Cosmetic - the update has actually completed. A Moongate plugin update restarts Moonraker as its final step, and KlipperScreen's update console watches the update through a live Moonraker connection. The restart cuts that connection mid-scroll, so the console freezes on the last line it received (usually one of git's `create mode 100644` file lines) and the "finished" message never arrives. Close the dialog and carry on, or restart KlipperScreen from Mainsail's **Services** panel if it won't dismiss. You can confirm the update landed under **Machine → Software Updates**: Moongate shows up to date. (Klipper itself is left alone on purpose: the plugin lives inside Moonraker, so a plugin update never needs to touch a running print.) ## The amber update arrow shows, but tapping "Update now" does nothing This happens on printers where Moongate was **installed by hand** (the machines in [docs/third-party-printers.md](docs/third-party-printers.md), like the Elegoo Centauri Carbon - or a manual file copy on a normal Pi). The one-tap update works through Moonraker's update manager, and a manual install isn't registered there - so on plugins up to 0.6.23 the button sent an update request the printer had no way to act on, and the arrow never cleared. From **plugin 0.6.24** the printer reports the difference honestly, and the app (**v0.9.61+**) shows re-copy instructions in the update dialog instead of the dead button (an older app still points at Mainsail's Software Updates panel, which doesn't list a manual install). Updating a manual install = re-copying the plugin file exactly as on install day (each machine's section in the third-party guide has the command), then restarting Moonraker; the arrow clears on its own within a few seconds. ## The Pi acts strangely after weeks of uptime (failed updates, sudo password errors) Plugin versions before **0.6.14** kept the remote-access proxy's request log on a small memory-backed disk (`/run`) that nothing trimmed. A printer left powered on for a few weeks could fill it completely, and a full `/run` makes unrelated things on the Pi misbehave: `sudo` fails with odd errors, updates act up, services get flaky. Check with: ```bash df -h /run ``` If it shows 100% (and `du -sh /run/*` blames `moongate-authproxy.log`), that's this. Fix: with the plugin on 0.6.14 or later, run the update chores **once by hand** over SSH (this step edits the proxy's service file, so it needs `sudo`, which is why a panel update cannot do it for you), then **reboot the Pi once** to empty the memory disk: ```bash bash ~/moongate/klipper-plugin/update.sh ``` Re-running the installer does the same. After that it can't recur: the proxy now logs almost nothing, and what it does log goes to the system journal, which cleans up after itself. (Until 25 September 2026 this page said a plugin update alone fixed it; it does not, because Moonraker never runs Moongate's update script, it only restarts Moonraker after the pull.) ## All your printers suddenly show offline (and you use a VPN) If every printer goes offline at once, especially after reinstalling the app or changing networks, and trying to connect shows a `trycloudflare.com` address failing, check whether a **VPN** on your phone is in the way. Moongate reaches your printers either on your home Wi-Fi or over a Cloudflare tunnel. A VPN reroutes the app's traffic through the VPN's own servers, which usually can't reach your home network or the tunnel, so every printer reads offline. - **If you use a VPN with per-app "split tunnelling"** (Mullvad and similar), Moongate needs to be on the **excluded / bypass** list so it skips the VPN. **Reinstalling the app removes it from that list**, so re-add Moongate to the split-tunnelling exclusions after any reinstall. - **Otherwise, turn the VPN off** and reopen Moongate. If the printers come back, the VPN was the cause. - The same applies to ad-blocker, private-DNS, and firewall apps that run as a "VPN" on Android. They can block the connection the same way. If a printer still shows offline with the right address after the printer's Pi has rebooted, **force-stop Moongate and reopen it**. The app caches a printer's remote address for a while, and a reboot can change it, so a force-stop makes the app fetch the current one. ## A printer you switched back on still shows offline If you power a printer back on (from Home Assistant, a smart plug, or its own switch) while Moongate is in the background, its tile can still read **offline** when you return to the app. To save battery, your phone can freeze Moongate while it's in the background, which briefly drops its live-status link - so on return the app still thinks the printer is off, even though the notifications may already show it back. Since **v0.9.45**, Moongate re-checks every printer's live status the moment you switch back to it, so the tile clears itself within a few seconds, no force-close needed. On older versions, force-stop Moongate and reopen it to refresh. Right after the power-on itself, also give the printer a little time: some Pis take a few **minutes** after boot to actually get their network address from the router (even when the printer's own screen already looks awake), and until that lands the tile stays offline no matter what the app does. It heals on its own - if the tile is still offline five minutes after the printer finished booting, then start with the checks below. ## Your print-status notification vanished, or notifications stopped updating If print notifications were on but the ongoing status notification is gone and you're no longer getting updates, you may have **paused** them. Since **v0.9.46** there's a **pause/play button** in the dashboard top bar (it appears when notifications are on): tapping **pause** stops Moongate checking your printers in the background, which saves battery and removes the ongoing notification. Tap the same button (now a **play** icon) to resume. A pause is remembered across app restarts and reboots, so it stays off until you tap play. ## No notification appeared on your iPhone - but everything says it was sent If you were **inside the Moongate app** (or any app, with Moongate in the foreground) when the alert fired, that's normal iPhone behaviour: iOS doesn't show a banner for a push notification while the app it belongs to is on screen. The alert still exists - swipe down from the top of the screen (Notification Centre) and it's there. Lock the phone or switch apps and the next one will banner normally. ## iPhone notifications show an old name for a printer (fixed in v0.9.71) Renaming a printer in the app only changed the name on the phone, while the notification title is built from the name you gave when you paired it, so a typo corrected in the app kept heading every alert. From **v0.9.71** a rename updates the server copy too, and the first time the dashboard opens after the update any name you changed earlier is sent up. Nothing to do beyond updating and opening the app once; no re-pair. Android alerts are built on the phone and were never affected. ## Android didn't alert when a print paused or failed, or the printer errored Loud Android alerts for these moments arrived in **v0.9.61** - older versions only re-labelled the quiet status bar. On v0.9.61+, three things have to be true, and each has an easy check: 1. **Print notifications are on and not paused.** Unlike iPhone push, Android's alerts come from the app's own background monitoring, so the "Print notifications" toggle in the menu must be on and the top-bar pause button not engaged. No status bar in the shade = nothing is watching, and nothing can alert. 2. **The "Printer alerts" category isn't muted.** Long-press any Moongate notification → notification settings, and check the **Printer alerts** category is allowed. It's separate from the quiet status/cards categories on purpose, so it can buzz (or be silenced) on its own. 3. **The moment happened while Moongate was watching.** Alerts fire on changes the monitoring sees as they happen. Something that went wrong while notifications were off, paused, or the phone was without any connection to the printer won't alert retroactively - the tile still shows the state and the reason when you open the app. **Custom `MOONGATE_NOTIFY MSG="..."` messages need plugin 0.6.26+ on the printer** to reach Android - older plugins push them to iPhones only. The app's monitoring picks them up from the printer itself, so the same three rules apply, and the Klipper console ack after the macro says which path it took ("notification sent" = pushed to iPhone and kept for Android; "message kept for the Android app" = no cloud push, Android still gets it). Update the plugin from the printer tile's plugin-update badge (app v0.9.66+; one tap on printers installed through Moonraker's update manager) or from Mainsail's Software Updates. Direct (LAN/VPN) printers are the documented exception: no notifications in cloud-free mode. ## The heat-soak alert never arrived (and how to be told when a heater is actually at temperature) The **Heat-soak alert** in a tile's Preheat sheet (long-press the temperatures while the printer is idle) reads the printer's **live temperatures** on every background check (rebuilt this way in v0.9.67; before that it was a plain countdown from the moment you pressed Set). With a soak time of 0 it fires the moment every temperature you set is reached ("At temperature: Bed 100° · Chamber 45°"); with a soak time it starts the clock once everything is at temperature and fires when the time is up ("Heat-soak complete: ... · soaked 20 min"). It is delivered by the same Android background monitoring as the alerts above, so the same rules apply, plus a few of its own: 1. **Android only.** iPhones get their alerts from the printer's push, which has no heat-soak event, so the sheet doesn't offer the alert there - use the *Start print?* dialog's rows or the `MOONGATE_TEMP_NOTIFY` macro instead (next section, plugin 0.6.27+), which the printer watches and pushes. 2. **Print notifications on and not paused** while it waits. The sheet warns (with a one-tap "Turn on") if they're off; the alert is still armed, so switching them on lets it fire, as long as the temperatures are reached within 6 hours of pressing Set. 3. **The "Heat alerts" category isn't muted.** It's a separate notification category so it can buzz on its own (renamed from "Heat soak timer" in v0.9.67; a mute you set carries over). 4. **"At temperature" means within 3 °C of the heater's *current* target.** A `PRINT_START` macro that retargets a heater while the alert waits moves the goal with it. A heater switched **off** (target 0) while it waits, or during the soak, cancels the alert quietly - the preheat was abandoned. A dip during the soak (a door opened) does **not** restart the clock. 5. **The Chamber box waits, it doesn't heat.** It appears only when the printer reports a chamber sensor, and on nearly every printer the *bed* is what warms the chamber - so the sheet insists on a bed temperature alongside a chamber one, and if the chamber can't get there on bed heat alone the alert never fires (the arm is dropped quietly after 6 hours). The rare printer with a real chamber heater (`heater_generic`) has the value set as a target too. 6. It fires on the next background check after the condition is met, so allow up to your refresh interval (default 30 seconds). **Want an alert on the real temperature, for any heater or sensor?** Klipper can wait for it and the `MOONGATE_NOTIFY` macro can tell you (plugin 0.6.25+ for iPhone, 0.6.26+ for Android with print notifications on): ``` [gcode_macro BED_READY] gcode: TEMPERATURE_WAIT SENSOR=heater_bed MINIMUM=100 MOONGATE_NOTIFY MSG="Bed is at 100" ``` For a chamber or other sensor use `SENSOR="temperature_sensor chamber"`. `TEMPERATURE_WAIT` holds the print queue until the temperature is reached, so use it in a "heat up, then tell me" macro or at the start of a print, not as a background watcher - or use `MOONGATE_TEMP_NOTIFY` (next section), which does not block. ## Preheat and soak before a print, "ready to remove" after it, and the `MOONGATE_TEMP_NOTIFY` macro (v0.9.68, plugin 0.6.27+) Since v0.9.68 the **printer** can do the temperature watching (plugin 0.6.27+): a watch waits for a set of temperatures, in whichever direction each one needs, optionally holds them for a soak time, then alerts **once** - as a push on iPhone and through the print-notification service on Android (plugin 0.6.26+ rules above), with the phone asleep or not. Three ways to arm one: - **Preheat and soak first** in the *Start print?* dialog (the folder button on an idle tile, pick a file, Start). Enter a bed temperature (pre-filled from the file's slicer metadata when it has one), a chamber temperature on printers that report a chamber sensor, and a soak time. The printer heats the bed (and a real chamber heater, where one exists - a passive chamber is only waited for, warmed by the bed), waits until everything is within 3 °C of target, holds it for the soak time, then **starts the print by itself** and tells you ("Heat-soak complete: Bed 110° · Chamber 45° · soaked 20 min · printing benchy.gcode"). It only starts if the printer is still idle at that moment; otherwise the alert says "not started, printer busy". - **Tell me when it's cool enough to remove**, same dialog. Moongate notes the bed and chamber readings when you tap Start and, once the print has run and ended (finished, cancelled or failed), alerts when both are back within **5 °C** of those readings ("Ready to remove: Bed 29° · Chamber 27° · benchy.gcode cooled in 47 min"). The helper text under the switch names the temperatures it will wait for. - **`MOONGATE_TEMP_NOTIFY`** from your own gcode: ``` [gcode_macro PRINT_END] gcode: ... park, heaters off ... MOONGATE_TEMP_NOTIFY BED=35 CHAMBER=30 MSG="Print cooled - safe to remove" ``` `BED=`, `EXTRUDER=`, `CHAMBER=` in °C (any subset), `SOAK=` minutes, `MSG=` the text (without it the alert reads "At temperature: Bed 35° · Chamber 30°" / "Cooled down: ..." / "Heat-soak complete: ..."), `CANCEL=1` clears. Each temperature's direction is decided when the macro runs: above the value now means wait for it to **cool** to it, below means wait for it to **warm** to within 3 °C. The command returns at once (the wait happens on the printer's own 20-second check, not in the print queue), so it is safe inside `PRINT_START` too - unlike `TEMPERATURE_WAIT`, which blocks. The console acks what it understood ("Moongate: will alert once Bed cooling to 35° · Chamber cooling to 30°."). If it didn't behave as expected: 1. **The dialog shows "Update the printer's Moongate plugin" instead of the two rows.** The printer runs a plugin older than 0.6.27; tap the update badge on its tile (or update Moongate in Mainsail's Update Manager). The preheat sheet's own Android heat-soak alert keeps working meanwhile. 2. **`MOONGATE_TEMP_NOTIFY` is "Unknown command".** The macro lives in `moongate.cfg`, which the installer writes once. From 0.6.27 the plugin adds any missing Moongate macro to an installer-managed `moongate.cfg` when Moonraker starts, but Klipper only reads it on its next **restart** - run `RESTART` (or Firmware Restart) once after the plugin update. A hand-edited `moongate.cfg` (header line changed) is left alone; copy the block from `klipper-plugin/install.sh`. 3. **Nothing happened, no alert.** A watch gives up quietly when it has waited **6 hours** without getting there (a chamber that can't reach the value on bed heat alone, heaters switched off during a warm-up, notifications off across the whole wait), and a "ready to remove" watch armed for a print that never started is dropped the same way. Cooling watches never cancel on heaters-off (that is the normal state). The tile shows "Waiting for temperature", "Soaking · N min left" or "Cool-down alert armed" while one is live, so you can see whether it is still armed; a tap on that line cancels it. 4. **The print did not start after the soak.** The printer was no longer idle when the soak ended (something else was started, or Klipper was restarted and the print queue changed); the alert says so. Start the file by hand. 5. **A Moonraker restart mid-soak** is fine - the watch is saved to disk and the soak clock carries on from where it was. A **Klipper** restart switches the heaters off, which cancels a warm-up watch (see 3). 6. One watch per source: a new `MOONGATE_TEMP_NOTIFY` replaces the previous macro watch, and the dialog's two rows each keep one watch per printer. The alerts land in the "Printer alerts" category on Android (the same one custom `MOONGATE_NOTIFY` messages use). ## Chamber temperature missing on the dashboard If a printer's chamber temperature doesn't appear on its tile even though it shows in Mainsail, update to **v0.9.32 or newer**. That release made chamber detection robust for combined chamber sensors (`temperature_combined`) and sensors named with capital letters, especially over the remote tunnel. **The chamber reading is wrong, or differs between home and away (several chamber sensors).** A printer with more than one sensor whose name contains "chamber" (Chamber, Chamber_Low, Chamber_Mid, Chamber_Toolhead) used to show one reading at home and another over the tunnel: the app preferred the sensor named exactly `chamber` but only on the home network, while the Pi plugin took the first match in your config order and that is what a remote dashboard saw. Fixed in **v0.9.77 + plugin 0.6.30**: both use the same rule, and you can pick the sensor yourself. **Hold the chamber temperature** on the tile (or the chamber cell on the Single-printer dashboard) and choose from every temperature sensor the printer has; the choice is saved for that printer and used at home, away and by the heat-soak and cool-down alerts (the alerts follow it once the plugin is on 0.6.30). A printer with no sensor named "chamber" at all has no chip to hold yet. ## No time remaining on a printing tile Since **v0.9.53** a printing tile shows the time left (or the projected finish time) after its temperatures. If it's missing: - **The print just started.** Since **v0.9.56** the readout appears within the first seconds of a print when the sliced file carries the slicer's own time estimate (almost all files do). A file without one waits until the estimate is meaningful - roughly a couple of minutes in (about 2% progress) - because too early, the maths would show nonsense. - **The print is paused.** A paused print's estimate can't be trusted (the print clock is frozen), so the readout hides until it resumes. - **The switch is off.** Check **menu → Dashboard Layout → Show time remaining** - the same place also switches between **Time left** ("~1h09m") and **Finish time** ("15:27"). The tile and the print notification share the same estimate, so they always agree. Since **v0.9.56** it's also computed the way **Mainsail's ETA** is - blending the slicer's estimate for the file, the filament used so far, and elapsed progress - so it should sit within a minute or two of what Mainsail shows on the same print (earlier versions extrapolated from elapsed time alone, which could read an hour or more too long early in a print). Like Mainsail's, it settles as the print runs. ## The light bulb shows the wrong state (on when the light's off) The bulb's on/off comes from the **Light Status Source** you set for that printer (**menu → Lighting**). If it's blank, the bulb just tracks your taps and assumes the light starts *off* - so it can read backwards. Set it to the light's Klipper object: the `[output_pin …]`, `[led …]` or `[neopixel …]` section name from your `printer.cfg` - e.g. `output_pin caselight` (the object name, **not** a raw pin like `PE3`). The bulb then follows that object's real value and self-corrects on each poll, even when you switch the light from Mainsail. ## Tile shows "Connected - Printer idle" This is **not** an error - it's the v0.4 way of saying "the Pi is reachable, but Klipper isn't producing usable status right now". Common causes: - The printer's power toggle inside Mainsail is off (Creality K3 and similar - Klipper isn't running until the printer-power switch is on). - Klipper crashed or is in an error state. Check `~/printer_data/logs/klippy.log` for the cause. - The Pi is rebooting and Klipper hasn't come back up yet. - **Just after restoring a backup:** the printer loads fine when you tap the tile, but the dashboard sits on "Connected - Printer idle". The Pi is likely on a **pre-v0.6.3 plugin** that doesn't yet recognise the restored app - update the plugin (*Mainsail → Update Manager*, or re-run the installer) and restart Moonraker. If the printer should be ready and the tile still shows "Connected - Printer idle": - Restart Klipper: `sudo systemctl restart klipper` - Restart Moonraker: `sudo systemctl restart moonraker` The tile will flip to live status within a couple of poll cycles after the underlying issue clears. Not sure which cause applies? **Menu → Report a problem** (in the app) sends a diagnostic report that records exactly why the status request failed - `404` (endpoint / proxy route missing), `401` (auth / owner), or `timeout` (slow or wrong network) - so it can be pinned down without guesswork. Since **v0.6.4** the report also carries the **remote (tunnel)** result and the **Pi's plugin version**, so an out-of-date plugin (a frequent cause of "works on LAN, fails remotely") shows up at a glance. ## Tile shows "Offline - Printer unreachable" The app's reachability probe got no response from either the LAN URL or the tunnel URL. Either the Pi is fully unreachable (powered off, network cable out, WiFi router down) or every service on it is down. Verify with: ```bash ping curl -s -o /dev/null -w "%{http_code}\n" http:/// curl -s -o /dev/null -w "%{http_code}\n" https:/// ``` - The first should respond. - The second should return `200` (or `301`/`302`) if nginx is up. - The third should return `401` if the auth proxy is running. ANY HTTP code (even 401, 502) proves the tunnel side is alive. If none of the three respond, the Pi is fully offline. If only the third fails, restart `moongate-tunnel` and `moongate-authproxy`. ## Tile shows "Tunnel" badge when I'm on home WiFi The app finds printers on your network two ways: the address it learned at pairing, and an automatic broadcast discovery (mDNS) that heals address changes. A tile stuck on **Tunnel** at home means both are failing for that printer - almost always a network quirk, not the printer: - **The instant fix: set the address by hand.** Open the printer's page, tap the **pencil**, and type its LAN address into **Printer address** (e.g. `192.168.1.50`). That field exists precisely for networks where discovery can't work. - **Separate 2.4GHz and 5GHz WiFi names?** Many routers block broadcast discovery *between* the two bands, so a phone on the "5G" network can never discover a printer on the 2.4GHz one. The manual address fixes it; joining the same band as the printer also works. - **Discovery answered with an unusable address (fixed automatically in newer versions).** On some networks, broadcast discovery hands the app a special link-local IPv6 address that can't actually be used - and it used to silently replace the working address, stranding the printer on the tunnel with everything else looking healthy. The app now discards those answers and keeps the address that works; if a tile that always said Tunnel at home goes Local after updating, this was why. - **Still stuck even with the right address typed in?** Check the router / access point for a "client isolation" or "allow WiFi devices to communicate with each other" setting - and if everything looks right, **reboot the access point**. A real field case: an AP had silently stopped forwarding traffic from its 5GHz clients to its 2.4GHz clients entirely; a reboot fixed everything at once. - **The Pi's IP changed** (a new DHCP lease)? Update the **Printer address** field - no need to remove or re-pair. When broadcast discovery works on your network this heals itself; the manual field is the belt-and-braces. Giving the printer a fixed IP in the router stops it recurring. - Guest networks and some mesh systems isolate devices from each other by design - a printer on one of those may only ever be reachable through the tunnel. ## The printer page says "Tunnel" while the tile says "Local" The opposite mismatch: the dashboard tile shows **Local**, but opening the printer shows an orange **Tunnel via Moongate** in its header. The tile re-checks its route constantly, but the printer page is kept loaded in the background so re-opening it is instant - and its local-or-tunnel choice used to be made only once, when it first loaded (often at app start, before WiFi was up or away from home), so a page that started on the tunnel stayed there. - **v0.9.57 and newer fix this automatically**: opening a printer checks the dashboard's live connection first, and a tunnel-riding page is rebuilt on the local network on the spot. - **On older versions**, tap the **refresh** button (top right of the printer page) - it reloads the page from scratch and picks the local network when it's reachable. ## iPhone: the printer page says "Cannot connect to Moonraker (yourprinter.local.)" on home WiFi The dashboard tile sits happily on **Local**, but the first open of the printer page shows Mainsail's "Connection failed" with the printer's *name* (ending `.local.`) in the message. This happens on iPhones that discovered the printer by name: the page's connection to Moonraker tries the printer's IPv6 address first, and most Pi web stacks listen only on IPv4. **Tap Try Again, or the refresh icon in the page's top-right - the retry connects.** Tracked as [issue #268](https://github.com/PEEKYPAUL/Moongate/issues/268) with a proper fix planned; Android is unaffected. ## Every printer away from home shows offline (orange crossed-out cloud in the top bar) That's the **Local only** switch (v0.9.48): while it's on, Moongate deliberately makes no remote connections, so only printers on your current network connect and everything else settles to offline. Tap the orange crossed-out cloud in the top bar to turn remote back on (a snackbar confirms), or turn the whole button off under the menu's **Local-only button** switch - switching the menu entry off also turns the mode off. The state survives app restarts, so a toggle you flipped days ago is the usual culprit. ## A Direct (LAN/VPN) printer shows offline A printer added through **Add Printer → Direct (LAN/VPN)** (v0.9.51+) has exactly one path to it - the address you gave it, over your network - so "offline" always means one of these: 1. **The phone isn't on the printer's network.** On WiFi at home this is your LAN; away from home it means your **own VPN must be connected** (WireGuard, Tailscale). Direct printers have no tunnel to fall back to - that's the mode's whole point. 2. **Moonraker is rejecting the phone.** The app talks straight to Moonraker, so the phone's network must be inside `[authorization] trusted_clients` in `moonraker.conf`. Home subnets usually already are; **VPN subnets usually are not** - WireGuard setups often use `10.x` ranges that happen to be covered, but **Tailscale's `100.64.0.0/10` almost never is**. Add the subnet and restart Moonraker: ```ini [authorization] trusted_clients: 192.168.1.0/24 100.64.0.0/10 # Tailscale ``` 3. **The Pi isn't actually in LAN-only mode.** Direct mode needs the plugin installed in LAN-only mode - answer **2) Direct (LAN/VPN)** when the installer asks, or pass `--lan-only` (see the README's LAN-only install section). Against a normal cloud-mode install the plugin still demands a token and answers `401`, so the tile sits offline. Check from any PC on the LAN: ```bash curl -s -o /dev/null -w "%{http_code}\n" "http:///server/moongate/status?mg_token=" ``` `200` = LAN-only mode; `401` = cloud mode (re-run the installer and pick **Direct (LAN/VPN)**, or pair the printer through the cloud instead). 4. **The Pi's address changed** (DHCP handed it a new IP). The app stores the address a Direct printer was added with - fix it in the printer's edit dialog (the pencil on its page), and give the Pi a **DHCP reservation / static IP** so it stops moving. Also by design: Direct printers have **no print notifications** (there's no cloud to send them), so a quiet notification shade for one isn't a fault. ## A Direct printer works at home but not over my VPN Same checklist as above with the VPN hat on: the VPN must be **connected on the phone**, its subnet must be in Moonraker's `trusted_clients` (point 2 above - this is the usual culprit, especially Tailscale's `100.64.0.0/10`), and the printer's stored address must be reachable *through* the VPN - for router-based WireGuard that's the printer's normal LAN IP; for Tailscale it's the Pi's Tailscale address, which is what you should have entered when adding the printer. ## Mainsail warns: "error detected while loading the moonraker component 'moongate'" Moonraker tried to load the Moongate plugin and couldn't. The two usual causes: 1. **The plugin file isn't where Moonraker looks** - a `[moongate]` section exists in `moonraker.conf` but `moongate.py` never landed in Moonraker's components folder (a silently failed download is the classic way; on machines with a sealed filesystem the copy itself can fail). Installing on a vendor / embedded printer? Follow [3rd-party printer support](docs/third-party-printers.md) - its per-machine steps avoid exactly these traps. 2. **A cloud-mode install is missing its Python libraries** (a broken or rebuilt Moonraker environment). From plugin **0.6.17** this no longer stops the plugin loading - it keeps running, and says exactly what's missing in `moonraker.log` and the `MOONGATE_PAIR` console output. On older plugins, reinstall: `curl -fsSL https://raw.githubusercontent.com/PEEKYPAUL/Moongate/master/klipper-plugin/install.sh | bash` Either way `moonraker.log` has the specific reason - search it for `moongate`. ## Dashboard tiles flicker or swap places every minute or so Some routers micro-drop the WiFi for a fraction of a second at regular intervals (re-authentication, access-point roaming). Before **v0.9.54** each drop could fail exactly one status check, briefly blanking a tile's webcam and temperatures - and, with **Auto-arrange by status** on, reshuffling the tile order as the printer dipped through "offline" and back. Update the app: from v0.9.54 a single failed check never changes what you see, and a printer only shows offline after two consecutive misses (a few seconds) - genuinely unplugged printers still read offline promptly. ## Opening a printer shows "the web interface isn't answering yet" From v0.9.48 this friendly message (with an automatic retry every few seconds) replaces the raw Cloudflare **"Bad gateway / Error 502"** page you used to see when opening a printer whose Pi was still starting up - the tunnel comes up a little before Mainsail does, so the first moments after a Pi boot can answer 502. It normally clears by itself within a minute. If it doesn't: check Mainsail loads in a browser on the printer's own network, and that Moonraker/Klipper are actually running on the Pi - the tunnel being up only proves the Pi is powered, not that the web stack behind it is healthy. ## The printer page turned into GitHub, Spoolman or the docs, and nothing brings Mainsail back Mainsail and Fluidd link out all over the place - a commit hash in the Update Manager opens GitHub, the AFC panel links to Spoolman, the sidebar links to the docs - and on a desktop those open in a new browser tab. Up to v0.9.69 the printer page in Moongate followed the link instead, and because Moongate keeps each printer's page alive for instant re-opening, the printer *became* that page: Back returned to the dashboard, opening the printer again showed GitHub, and nothing in Moongate led back. From **v0.9.70** a link to anything other than the printer's own address opens in an in-app browser tab (Android) or a Safari view (iPhone) with its own Close, and the printer page stays put; a page that has still ended up elsewhere is put back on the printer when you open it. Away from home (over the tunnel) a link into the printer's own network, such as Spoolman on the Pi, shows a note that it can't be reached from there: the tunnel only carries the printer page itself, not the other services on the Pi. On an older build, the **refresh arrow** in the printer page's top bar reloads the printer from scratch, which brings Mainsail back. ## A file you expect is missing in File System (v0.9.63+) The file browser hides clutter by default: Klipper's timestamped SAVE_CONFIG snapshots (`printer-20260830_101530.cfg` and friends), `.bak`-style backup files, other tools' timestamped config copies (crowsnest-style, `crowsnest.conf.2024-12-22-1121`; from v0.9.64 these also open as plain text if you show them), and hidden dotfiles and dot-folders (`.theme/`, `.mainsail.json`) - along with any folder left empty by the filter. Untick **Hide backups & hidden files** at the top of the sheet to list everything; the choice is remembered. ## Remote tunnel not connecting - Check the systemd unit: ```bash sudo systemctl status moongate-tunnel ``` - View the captured stdout (this is where the tunnel URL appears): ```bash cat /run/moongate-tunnel.log ``` - The Cloudflare Quick Tunnel URL changes on **every** restart of `cloudflared`. The app fetches the latest URL automatically - you do not need to re-pair when this happens. - **Plugin 0.6.23+ tells you what's wrong, in plain English.** Run **`MOONGATE_STATUS`** in the Klipper console (Mainsail/Fluidd → Console). It checks both halves of remote access live - can the Pi reach the cloud, and does its public tunnel actually answer from the internet - and prints each verdict with its fix (re-pair, sync the Pi's clock, which service to check). It also refreshes the printer's cloud check-in as a side effect. - **Plugin 0.6.23+ also heals a dropped tunnel by itself.** If the tunnel goes unreachable from the internet while the Pi's own connection is fine (a common outcome of an ISP or router hiccup that `cloudflared` doesn't notice), the plugin spots it after its next check-in and **restarts the tunnel within ~15 minutes**, publishing a fresh URL with nothing for you to do. If a printer keeps needing this, that points at the Pi's network (Wi-Fi signal, a router evicting the connection), not at Moongate - `MOONGATE_STATUS` shows the self-heal count so you can tell. ## Re-pairing takes minutes to show online After re-pairing a printer (reinstalling the app, moving to a new phone, or running `MOONGATE_RESET_OWNER`), the tile can sit on "Starting up…" for several minutes before it connects - most noticeable on networks where the app can't reach the Pi directly over local WiFi. Fixed in **plugin 0.6.7+**: the Pi now reports its connection to the cloud within seconds of the re-pair, instead of waiting for its next 5-minute check-in. **Plugin 0.6.9** hardens this further - if the Pi hits a brief network hiccup at that exact moment (common on flaky-WiFi boards), it keeps retrying every few seconds rather than dropping back to the 5-minute wait. **Re-run the Pi installer** (or update via **Mainsail → Software Updates → Moongate**) to get the latest. Scanning the QR instead of typing the GATE code also connects instantly over local WiFi. ## Auth proxy returns 401 for me, not just attackers If your own app is getting 401s from the tunnel side (status tile is stuck offline, but LAN works): - Verify the access proxy is running and the plugin can sign valid tokens: ```bash sudo systemctl status moongate-authproxy journalctl -u moongate-authproxy -n 50 ``` - Check `~/.config/moongate/owner.json` exists and references your user. If it's missing or refers to a stale pair, run `MOONGATE_RESET_OWNER` in the Klipper console and re-pair. - A token mismatch can happen if the device signing key was regenerated (uninstall / re-install / manual `~/.config/moongate/` wipe). The app's cached token becomes invalid. Force-close and re-open the app - it'll refresh on next poll. ## Remote access fails over the internet but LAN works (v0.6.3 Pi) If a printer is healthy on home WiFi but every **remote (tunnel)** request fails - typically a **500** - and the Pi was last updated around **v0.6.3**, this is the v0.6.3 auth-proxy regression. The proxy failed *closed* (it denied every request - nothing was exposed), which is why LAN access kept working and it slipped through. **Fix:** update the Pi to **v0.6.4 or newer** (*Mainsail → Update Manager*, or re-run the installer), then let the services restart: ```bash curl -fsSL https://raw.githubusercontent.com/PEEKYPAUL/Moongate/master/klipper-plugin/install.sh | bash ``` A bug report (see above) now shows the **Pi's plugin version**, so you can confirm the Pi is on v0.6.4+ afterwards. ## Works on home WiFi, never away from home, and the Pi's clock is wrong Every remote request carries a short-lived signed token and the Pi checks its timestamp against its own clock. A Pi whose clock is more than a minute out (no time sync after a power cut, or a network that blocks NTP) rejects every token, so the printer works on the LAN and never over the tunnel. Check on the Pi with `timedatectl` - you want `System clock synchronized: yes`. If NTP is blocked, `sudo apt install htpdate` sets the clock over plain HTTP instead. Until v0.9.71 the app also reacted to each rejection by fetching a fresh token on every poll, which cost you nothing but hammered the server; the app now backs off after the first retry, so the printer simply shows offline until the clock is right. ## Remote shows "500 Internal Server Error" but LAN works (plugin 0.6.20) If every **remote (tunnel)** request answers a plain "500 Internal Server Error / Server got itself in trouble" page while home-WiFi access, pairing and the tunnel indicator all look healthy, the Pi is on **plugin 0.6.20** (installed or updated between 29 Jul and 3 Aug 2026). A memory-saving change in 0.6.20 left the tunnel gatekeeper without the signature-checking library it needs, so it crashed on every remote request instead of answering. Nothing was lost or exposed: it failed *closed*, and local access never goes through that gatekeeper, which is why everything else kept working. **Fix:** update the plugin to **0.6.21 or newer** (*Mainsail → Machine → Update Manager* - refresh, then update **moongate**; or re-run the installer). Remote access returns as soon as the services restart - no re-pairing and no app update needed. ## The print queue (v0.9.78+) The queue button on a file in **Start a print** adds it to Moonraker's print queue (the same queue Mainsail shows); the **Queue** chip there, or in the Single-printer dashboard's tool row, opens the queue sheet; a tile shows **Next: file** once something is queued. - **The queue ran one job and then stopped.** That is Moonraker's default: with `automatic_transition` off it pauses the queue after each job it loads, so each **Start queue** runs one job. To run the whole queue unattended, add to `moonraker.conf` and restart Moonraker: ```ini [job_queue] automatic_transition: True ``` The sheet's footer tells you which mode the printer is in (it reads the setting through plugin 0.6.31+). - **No "Up next" line on the tile although files are queued.** The line needs plugin **0.6.31** on that printer (the tile shows the update badge); the queue sheet itself works on any plugin version because it talks to Moonraker directly. - **"The printer did not answer" when adding a file.** Moonraker rejects the whole add when any named file is missing from the printer (deleted or renamed since the list was loaded); refresh **Start a print** and try again. The same message appears when the printer is unreachable on both paths. - **I cannot drag jobs into a new order.** Moonraker's queue only supports moving a job to the front, which is the arrow on each row; there is no free reorder in Mainsail either. - **A Direct (LAN/VPN) printer** queues exactly the same way; nothing goes through the cloud. ## The G-code layer viewer (v0.9.72+): what it shows, and when it says it can't The layers button on a file in **Start a print** opens it as a 3D layer stack. One finger spins and tilts, pinch zooms, two fingers pan, double tap resets, and the rail on the right steps through the layers (hold an arrow to run). The chips above the picture pick the view: **Temperature**, **Flow rate**, **Layer time**, **Speed** or **Fan speed**, coloured like Orca's preview from the file's lowest value (blue) to its highest (red), or **Colour** (v0.9.73+), every line in the filament it was printed with and a key of one swatch per tool. The ⋮ menu's **Opens with** picks the first view: **Auto** opens Colour for a multi-material file and Fan speed for anything else; **Last used** or a fixed view if you prefer. Things that are normal: - **Everything is one colour.** The file holds a single value for that map - one temperature all the way through, or the fan at 80 % on every layer. The legend shows that value at both ends. Orca does the same. In the Colour view a single-filament file is one colour by definition; the key has one swatch. - **The Colour view's key says "Colours chosen by the app (none in the file)".** The slicer did not write filament colours into the file. Orca, PrusaSlicer and SuperSlicer do (`; filament_colour =` in the settings block); Cura and Simplify3D do not, so the viewer uses its own six-colour palette, one per tool. The tools are still told apart correctly; only the shades are not yours. - **The key says "Colours chosen by the app (the file's are all the same)".** The file names a colour for every tool, but it is the same one throughout, typically the same filament preset loaded in every slot. Painting with it would make a multi-material print one colour, so the viewer uses its palette instead. A PrusaSlicer or SuperSlicer file is read the way its own preview paints it (v0.9.74+): the colour set on each extruder in the Plater (`; extruder_colour =`) first, the filament preset's colour only where the extruder has none. Before v0.9.74 only the filament colour was read, which is why the first tool-changer test came out all off-white. - **Save to phone** (v0.9.74+): the download button next to View on a file, or the viewer's ⋮ menu, downloads the whole file from the printer and opens the phone's "save as" sheet (Android's file picker, the Files app on iPhone). It lands wherever you choose there. Over the internet a file above 40 MB asks first. Since v0.9.76 the app keeps a copy for the rest of the session (its own cache folder, about 300 MB, oldest out first, emptied every time the app starts), so viewing then saving, or opening the same file twice, downloads it once. - **The Colour view has a Sliced / Loaded switch on one printer but not another** (v0.9.76+). The switch appears when the printer's plugin (0.6.29+) reports what is loaded per tool, which it reads from Happy Hare (`mmu`) or AFC (`AFC_lane`). A bare tool changer without Happy Hare has no such data in Klipper, and a printer on an older plugin sends none; update the plugin from the tile badge. Direct-mode and third-party printers have no plugin, so no switch. - **Loaded shows a grey hatched swatch and "empty".** Happy Hare reports that gate's status as empty, or the AFC lane's prep and load sensors see no filament. Load the gate (or fix the gate map) and the key updates on the next poll when you reopen the viewer. - **Hold a thumbnail to see it big** (v0.9.76+): the biggest picture the slicer embedded, with the file name; tap anywhere to close it. A tap on the picture selects the row as before. "No preview picture in this file" means the slicer wrote no thumbnail. - **An MMU print (ERCF, Box Turtle, Tradrack under Happy Hare) shows one tool.** Fixed in v0.9.75. Happy Hare's Moonraker preprocessor rewrites the slicer's `T1` lines to `MMU_CHANGE_TOOL TOOL=1` when the file is uploaded, and older versions of the viewer only read `T` lines. Update the app; the file on the printer is fine as it is. - **A single-extruder colour-change print shows more tools than the printer has.** Each `M600` filament change counts as the next colour (PrusaSlicer's painted prints, Orca's manual filament change), so a four-colour print on a one-hotend printer shows T0 to T3 in the key. That is what the file does, not a detection fault. - **Temperature says "Not set in this file (a macro sets it)".** The file never states a hotend temperature the viewer can read: no `M104` / `M109`, no `SET_HEATER_TEMPERATURE`, and no temperature argument on `PRINT_START` (`HOTEND=`, `EXTRUDER_TEMP=` and the usual spellings are all read; an opening `M104 S0` does not hide them). Typically the macro on the Pi has the temperature hard-coded. The other four maps still work. - **Fan speed shows the commanded value**, so a fan Klipper scales with `max_power` reads higher here than on the printer. - **Over the internet a big file asks first** (above 40 MB). On home WiFi it never asks. A 12 MB file takes about a second on WiFi and a few seconds over the tunnel. - **Not shown by design:** start and end markers, the nozzle position, travel moves. **"Couldn't load this file":** the download or the parse failed; the small line under the message says which. *download* means the printer did not hand the file over (the same reasons a thumbnail would not load: the printer went offline, the tunnel dropped, the file was deleted or renamed meanwhile) - Retry once the tile is back online. Anything else is a parsing error worth sending with a bug report (Settings → Send feedback) together with the file name and the slicer that made it. **"No printable moves found"** means the file has no extrusion moves the viewer understands - a binary `.bgcode`, or a file that only runs macros. ## Webcam not showing - The app uses the snapshot path Moonraker reports for your webcam - typically `/webcam/?action=snapshot` for mjpg-streamer setups. - Make sure your webcam is configured in Mainsail / Fluidd under Settings → Webcams, and the snapshot URL works in a browser on your LAN. - **Says "Camera unreachable" while the same camera works in a browser?** Check the tile for the **amber update icon** first: a printer on an old plugin can mis-report its camera's address to the app even though Mainsail itself is fine (plugins before **0.6.17** had exactly that bug). Update via *Mainsail → Software Updates*, and the tile heals by itself within seconds of Moonraker restarting - nothing to clear or re-pair. From **v0.9.61** the message itself tells you when the printer's plugin is the likely cause. - If you don't have a webcam, the tile falls back to the **Mainsail or Fluidd logo** (whichever you run). That's the expected v0.4 behaviour - it's not an error. ## A custom / external camera isn't showing (v0.9.0+) You can point a tile at a camera that isn't connected to Klipper (e.g. an old phone running an IP-webcam app) via the **gear** in the corner of the tile's camera. If it doesn't appear: - **Use the camera's snapshot or stream URL** - the same address that works in a browser on your LAN (e.g. `http://192.168.0.107:8080/video`). The tile pulls one frame at a time, so a stream URL is fine; the camera's web-UI *page* URL is not. - **On Wi-Fi but blank?** Confirm the URL opens in your phone's browser while on the same network. The app fetches the camera directly on the LAN, so if the browser can't reach it, neither can the tile. - **Works on Wi-Fi but not remotely (cellular)?** Remote cameras go through the Pi, which needs the **v0.9.0 / plugin 0.6.8+** update - re-run the Pi installer (or *Mainsail → Software Updates*) and let it restart. Also note: only **home-network (private) cameras** can be reached remotely; a camera on a public address won't relay through the tunnel, by design. The URL must also be a **numeric LAN address** over plain `http` (like `http://192.168.1.20:8080/video`) - a name like `http://mypi.local/...` or an `https://` address works on your Wi-Fi, where the phone fetches the camera directly, but won't relay remotely, so the tile sits on the logo away from home. - **Auto-detected camera wrong or missing?** With **app v0.9.59+ and plugin 0.6.22+**, a printer with several webcams gets a **switch button** on its tile's camera - pick the one you want and it sticks. On older plugins Moongate reads the *first* webcam from Mainsail's webcam list: set the one you want by hand with the gear, or reorder them in Mainsail. A gear-set camera always beats the picker. - **(v0.9.61+) A dead custom URL steps aside instead of blanking the tile.** When a hand-set address keeps failing and the printer reports a camera of its own, the tile shows the printer's camera with a tappable **"Custom camera unreachable"** notice - tap it to fix or clear the URL (the override is never deleted behind your back, and editing it puts it back in charge). While any custom URL is active the tile's gear also wears a small **amber dot**, so a forgotten override can't silently hide a printer's real camera. - **Gears in the way?** Turn them off under **Menu → Camera config icons** - that hides every tile's gear without affecting the camera feed. ## Only one of a printer's several cameras shows (v0.9.59+) The camera switch button (top corner of the tile's camera box) appears only when **all** of these hold: - **The printer runs plugin 0.6.22 or newer** - older plugins report only their first webcam, so the app has nothing to switch between. Update via *Mainsail → Software Updates* (or re-run the installer) and let Moonraker restart. - **Two or more webcams are enabled** in Mainsail/Fluidd's webcam settings - disabled entries don't count and don't appear in the picker. - **The tile has no hand-set camera** - a camera pasted into the tile's **gear** overrides everything, so the switch hides rather than fight it. Clear the gear override to get the picker back. The picker lists cameras under the names they have in Mainsail (a nameless entry shows as "Camera 1", "Camera 2", …). Adding, renaming or removing cameras happens in Mainsail/Fluidd as usual; the app follows along on its next status poll. Your pick is per printer, survives restarts, and travels in backups. ## A go2rtc camera: tiles work, but it's black or freezes inside Mainsail (v0.9.52+) From v0.9.52 the app understands **go2rtc** cameras (a `stream.html?src=...` address in Mainsail's webcam settings): the dashboard tile and the full-screen camera show them as live stills automatically, whether auto-detected from Mainsail or pasted into a tile's gear. Away from home a go2rtc camera follows the external-camera rules above: the address in Mainsail's webcam settings must be a numeric LAN `http://` URL (like `http://192.168.1.20:1984/stream.html?src=cam`), or the tile shows only the logo remotely. The **printer page** is different: there you're looking at the real Mainsail, which plays the camera as live video with go2rtc's own player. If that video stays black, says "disconnected", or **plays a few seconds and then freezes** - and does the same in Chrome on your phone or PC while Firefox plays it fine - the cause is your camera's video encoding, not the app: the stream contains **B-frames**, which the WebRTC video protocol can't carry, so every Chrome-based browser (including the app's built-in one) gives up on it. Two fixes, either works: - **Stop producing B-frames.** If go2rtc encodes the camera itself (an `ffmpeg:` source), remove `#hardware` from the stream line, or keep it and append `#raw=-bf 0`. If the camera encodes its own H.264, look for a B-frames / GOP option in its settings, or pick the "baseline" H.264 profile. - **Sidestep the codec entirely** by giving Mainsail go2rtc's MJPEG endpoints instead: service type *MJPEG-Streamer / IP-Camera*, stream `.../api/stream.mjpeg?src=NAME`, snapshot `.../api/frame.jpeg?src=NAME`. Works in every browser; uses more bandwidth than WebRTC. After **any** webcam config change, tap the **refresh icon** in the top-right of the app's printer page - the app keeps each printer's Mainsail page loaded for instant opening, so without a refresh it can keep showing the old camera setup. ## A USB webcam through go2rtc: "Connecting…" everywhere after a reboot, or the picture crawls at 1-3 fps Two go2rtc-with-a-USB-webcam problems look like app faults but live on the Pi. Both show the same way from **every** viewer at once - the app's tiles, Mainsail's camera panel on a PC, and a plain `curl http://:1984/api/frame.jpeg?src=NAME` all wait forever or crawl - and that is the tell that the app is not the cause. - **Everything says "Connecting to …" after the Pi rebooted, and go2rtc's log says `device or resource busy`.** The kernel put the idle camera into USB autosuspend a few seconds after boot and the wake-up failed: `dmesg` shows `uvcvideo … reset_resume error -13` right after a `reset high-speed USB device` line. From then on the camera answers nobody until it is re-plugged. Recover without a reboot: stop go2rtc, re-enumerate just the camera's port - `echo 0 | sudo tee /sys/bus/usb/devices//authorized; sleep 2; echo 1 | sudo tee /sys/bus/usb/devices//authorized` (the port is the `usb 1-1.x` name next to the camera in `dmesg`; leave the printer's own USB devices alone) - and start go2rtc again. Stop it recurring with a udev rule that keeps the camera awake, in `/etc/udev/rules.d/`: `ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="046d", ATTR{idProduct}=="082d", TEST=="power/control", ATTR{power/control}="on"` (your camera's vendor and product ids come from `lsusb`). - **The picture crawls at 1-3 fps once the room is dark, and is fine in daylight.** Many UVC cameras (the Logitech C920 included) ship with "dynamic frame rate" on: in low light they slow the sensor right down to gather light, and every viewer gets those few frames. `v4l2-ctl -d /dev/video0 -c exposure_dynamic_framerate=0` restores the full rate immediately, at the price of a slightly darker, noisier picture with the lights off (older kernels call the control `exposure_auto_priority`). The setting resets whenever the camera re-enumerates, so put it in the same udev rule or in the go2rtc service's `ExecStartPre`. Set camera controls **one per `v4l2-ctl` call** - a C920 refuses `focus_absolute` in the same call that turns autofocus off, and the whole call fails. To tell the two apart, ask go2rtc itself: `curl -s "http://:1984/api/streams?src=NAME"` twice a few seconds apart. The producer's `packets` counter is the camera's real frame rate; each consumer's `packets` and `drops` are what that viewer received and lost. A producer stuck on the same number is the first problem, one climbing by about 3 a second is the second, and a producer climbing by 30 while the consumers drop most of it means the network between the Pi and that viewer is the limit, not the camera. ## A tile shows the logo, or a printer has no camera at all Newer app versions show a small **"Camera waking up…"** spinner in the camera box while the first picture is still being fetched - on-demand cameras (like go2rtc) genuinely take a moment to start when nobody was watching. The spinner gives up after about half a minute, and what replaces it tells you what's wrong: - **"Camera unreachable, check its address"** - every attempt got an error back: the address set in the tile's **gear** is wrong or stale (a camera that moved to a new IP), or nothing is listening there any more. Fix the address in the gear (or power the camera back on) and the picture returns by itself - nothing to restart. The spinner also stays gone on a camera the app already knows is dead, instead of pretending afresh every time you scroll past it. - The plain **Mainsail/Fluidd logo** - the feed really isn't coming for a quieter reason: the printer is offline or connecting, it has no camera configured, or the camera never answered at all. From v0.9.70 a printer with no camera entry in Mainsail / Fluidd and nothing in the gear always lands here: the app quietly tries the standard `/webcam/` path in case a stock Crowsnest answers, and when nothing does that is simply a printer without a camera. "Check its address" is kept for a camera somebody actually set up. The full-screen camera view (the eye on a tile, or the camera icon on the printer page) still works whenever there's a feed. Two diagnostics if a camera stays on the logo: - The **in-app bug report** (menu → Report a problem) now includes what each tile's camera fetch is actually doing - which address it tried and what came back - so a report tells us (and you) where it stops. - Away from home, remote camera pictures travel via the Pi, and the Pi now keeps a note when that fails (plugin 0.6.18+): `journalctl -u moongate-authproxy` on the Pi names the reason - a rejected camera address, a camera error, or a camera that answered with nothing (typically a camera whose device is stuck; a replug or Pi reboot usually revives it). If a printer has **no camera area at all** (a slim, compact tile), its webcam is switched **off**: open **Menu → Webcams** and turn it back on. Turning a printer's webcam off there collapses its tile to save space and data; turning it on restores the full tile with its feed. ## The power button isn't showing on my printer's tile The power button (bottom-left of a tile's camera) appears only when **Moonraker reports a power device** - a `[power …]` section in `moonraker.conf` (any type: GPIO, Shelly, TP-Link, Tasmota, …). It's detected automatically; there's no app setting. If it's missing: - **No power device configured.** Add a `[power ]` section to `moonraker.conf` and restart Moonraker. - **Moonraker isn't reachable.** The button needs to reach Moonraker over WiFi or the tunnel. A printer powered *off* by its own device is fine - Moonraker stays up, so the button shows (dim) and can switch it back on. But if the whole **Pi** is off, nothing answers and no button shows. Tapping it opens a popup offering **Turn on** and **Turn off** (the one that changes the current state is highlighted), so a wrong on/off reading never stops you sending the command you meant. While the printer is **printing or paused** the button is **greyed out** (v0.9.69+; older versions only greyed it for a `locked_while_printing` device), so power can't be cut mid-job. On the Single-printer dashboard it sits in the same bottom-left corner of the camera. ## "Power all machines" skips a printer, or won't switch a macro printer The top-bar **Power all machines** button (switch it on in the menu) powers your whole fleet at once, and it only shows the buttons your fleet supports. A printer that's **printing** is always left on, and one that's **offline** is skipped. If a printer's power is a **Klipper macro** (the Advanced Power Switch) rather than a Moonraker power device, give it a **Power Off** (and/or Power On) macro and the fleet button will use it. A single **toggle** macro works only from that printer's own tile button, not from Power all machines, because a toggle can't tell which way to switch a whole fleet at once. Most macro setups are **power-off only** (you can't switch a machine back on with a Klipper macro once it's off), and that's fine: the fleet button just shows that printer as "off only". ## What's the red triangle on a tile? (Emergency stop) Every **online** tile shows a small red warning triangle by its temperatures - it's an **emergency stop**. **Double-tap** it to halt the printer immediately with Klipper's `M112`; a single tap does nothing, so a stray touch can't fire it, and there's no confirm dialog (the double-tap *is* the safeguard). After an emergency stop the machine is **shut down**, not just idle - so the triangle turns into an **orange restart button**. Tap it once to run `FIRMWARE_RESTART` and bring the printer back online; the triangle returns once it's healthy. The restart is a deliberate manual tap rather than automatic, since an emergency stop can mean something's worth checking first. On the Single-printer dashboard the same triangle (and the restart button after a stop) sits at the right of the header, larger, and stays on screen while you scroll. ## The Single-printer dashboard (v0.9.69+): missing menu options, no arrows, or the position shows "--" **Menu → Dashboard layout → Single-printer dashboard** shows one printer full screen instead of the tile grid (a fresh install asks "One printer or several?" straight after the language). Untick it any time to go back to the tiles, the Multi-printer dashboard. - **The column picker, "Auto-arrange by status" and "Show dashboard buttons" disappeared from the menu.** They only apply to the tile grid, so they hide while the Single-printer dashboard is ticked. Untick it and they come back with your settings unchanged. - **No ‹ › arrows next to the menu.** They appear only when you have two or more printers. Tap the printer name at the top to pick from the full list, which also has **Add printer**. - **The arrows follow your own order, not the status order.** They use the order you arranged the tiles in (or the order you added the printers), never "Auto-arrange by status", so they don't reshuffle while prints start and finish. - **The buttons are greyed out for a moment after switching printers.** Only the printer on screen is refreshed, so the screen waits for that printer's first fresh reading (a thin bar under the header) before it lets you pause, start or home anything. On your home network that takes a second or two, over the tunnel a few seconds. - **The position shows "--", "Position unknown" or "Not homed".** The X Y Z readout comes from the printer on every refresh. "--" means no reading yet; "Position unknown" means Klipper is shut down or not ready (restart it with the orange button in the header); "Not homed" means the printer hasn't homed since it started - tap **Home** (only while the printer is idle) or use **Move** for the Control panel's jog pad. - **No temperatures or position at all, just "Connected - Printer idle" or "Offline".** Same meaning as on a tile: see [Tile shows "Connected - Printer idle"](#tile-shows-connected---printer-idle) and [Tile shows "Offline - Printer unreachable"](#tile-shows-offline---printer-unreachable). ## In-app update didn't install, or asks for a permission > **Early-access APK build only.** The App Store and Google Play builds update through their store and never show this flow. Tapping **Update** downloads the new version inside the app, then hands it to Android's package installer. The **first** time, Android asks you to allow Moongate to **install unknown apps** - expected for an app installed outside the Play Store. Grant it (the system jumps you straight to the toggle), come back, and tap **Update** again; after that it's a single tap plus the standard install confirmation. If the download or install fails for any reason, Moongate **falls back to opening the APK in your browser** - finish there and open it to install. Either route installs **in place** over your existing app (same signing key), so your printers and settings are kept. ## An amber update icon appeared on a printer's tile That printer's **Moongate Pi plugin is older than the one this app version shipped with** (v0.9.50+). It's a reminder, not an error - the printer keeps working - and it stays until the plugin is updated, then clears by itself on the next status refresh. Tap the icon: - On plugins **0.6.16 or newer** you get an **Update now** button - one tap and the printer fetches and applies the update itself in the background (about a minute). The button is greyed out while that printer is **mid-print**; update after the print finishes. - On **older plugins** the dialog points you at the printer's web interface instead: **Mainsail (or Fluidd) → Software Updates → Moongate → Update**. After that first manual update the one-tap path is available forever. If a one-tap update doesn't seem to take (the icon never clears), do it once from Mainsail's Software Updates panel - the usual cause is a hand-installed plugin that Moonraker's update manager doesn't know about. ## The Klipper console says the printer was released and "cloud contact is paused" This appears when the printer's cloud registration is gone - usually because the printer was **removed in the app** (or the record was reset). Rather than checking in every few minutes forever, the plugin (0.6.15+) goes quiet and waits. **Nothing about the printer itself is affected** - printing, Mainsail, and LAN use all keep working. To reconnect it to the app, just run **`MOONGATE_PAIR`** in the Klipper console and pair as normal - pairing instantly wakes the plugin, no restart or reinstall needed. **Saw this message right after running `MOONGATE_PAIR`, and the printer stayed offline in the app even though pairing succeeded?** That was a bug in plugin 0.6.15-0.6.18: the quiet-down could kick in mid-pair, so the freshly paired printer never told the cloud its address and sat "offline" or "Starting up…" in the app. **Fixed in plugin 0.6.19** - update via Mainsail's Software Updates (or the amber tile icon). On an older plugin the workaround is simple: after the printer appears in the app, run `MOONGATE_PAIR` once more (or restart the Pi) - the tile comes alive within seconds, and the extra pairing code it prints just expires unused. ## QR scan won't work / camera fails - Grant camera permission when prompted, or via **Settings → Apps → Moongate → Permissions → Camera**. - The QR scanner only works in **release** builds with the ProGuard rules in [`mobile/android/app/proguard-rules.pro`](mobile/android/app/proguard-rules.pro). Debug builds also work; R8 doesn't run in debug. - If the camera opens but fails to read the code: type the **GATE code** instead. Tap **+** to open Add Printer; the two 4-digit boxes for the `GATE-XXXX-XXXX` code shown in the Klipper console are right below the Scan QR button (numpad keyboard). - **Only have the one phone** (the QR ends up on the same screen that would scan it)? Type the GATE code as above, or for a Direct (LAN/VPN) printer just enter its address. Newer plugins also show a **"Pairing with this phone? Tap here"** button on the pair page - if tapping it does nothing, your app version doesn't support tap-pairing yet (it arrives in an upcoming update); use the code or address route meanwhile. ## Pairing fails / "already paired" error In **v0.4.2+** the recovery is one macro: `MOONGATE_RESET_OWNER` on the Pi (Klipper console) wipes the local owner record **and** releases the cloud-side association (the Pi signs the release request with its own device key - same key it already uses for heartbeats). Then `MOONGATE_PAIR` and pair again from any app install. Before v0.4.2 the cloud row could be orphaned by a fresh app install (new anonymous identity), which would cause `already_paired` on re-pair. That's gone now - the Pi can clean up its own cloud row without the original app being reachable. ## All tiles offline after reinstalling the app (or a new phone) **Symptom:** You reinstalled Moongate, or switched to a new phone, and every printer shows offline - even on your home WiFi. **Cause:** A fresh install creates a new anonymous app identity. Your printers are still associated with the *previous* identity in the cloud, so a brand-new install owns nothing. **Fix (v0.6.3+): restore from a config backup.** If the backup was made by **v0.6.3 or newer**, it carries a single-use restore code that re-links your printers to the new install - they come back **online automatically, with no re-pairing**. Use **Menu → Restore config**, or **Import config from file** on the Add Printer screen. Each Pi must be on the **v0.6.3+ plugin** (update via *Mainsail → Update Manager*, or re-run the installer) so it recognises the restored app - otherwise restored tiles sit on "Connected / idle" (see above). Since **v0.6.4** the app is explicit about the result - it tells you which printers actually came back online and which still need a re-pair, instead of always reporting success. **Restoring onto a dashboard that already has printers (v0.9.47+):** restore is a **merge** - printers you've paired since the backup was made are **kept**, with the backup's printers added alongside them. If the backup doesn't include some of your current printers, a dialog offers **Keep them** (the default), **Remove them** (make the dashboard match the backup exactly), or **Cancel** - cancelling changes nothing and does **not** spend the backup's single-use restore code. (Before v0.9.47, restoring always made the dashboard match the backup exactly, which could silently drop a newly-paired printer.) **If your backup predates v0.6.3** (no restore code), or you don't have one, re-pair each printer: on the Pi run `MOONGATE_RESET_OWNER` then `MOONGATE_PAIR`, and scan the QR (or type the GATE code) in the app. > **Best practice:** back up your config *before* you uninstall - a v0.6.3+ backup carries the restore code, so restoring on the new install brings everything back online. ## Tunnel URL leakage - what's actually exposed in v0.4? **Nothing.** This was a real concern in v0.2.x where the tunnel terminated at nginx serving Mainsail without auth. In v0.4 the tunnel terminates at the auth proxy, which returns flat 401s for every request without a valid short-lived token. The URL alone is useless. Share it with anyone - they get 401s. See [SECURITY.md → "What the tunnel actually exposes (v0.4)"](SECURITY.md#what-the-tunnel-actually-exposes-v04) for the empirical 35-vector attack-matrix verification. If you're still running v0.2.x: upgrade. The known browser-direct-to-Mainsail hole was the explicit driver for v0.4.0. ## Moonraker behind a reverse proxy (Traefik, Caddy, NPM) or in Docker Moongate assumes the standard Klipper layout: it connects to your printer **directly over plain HTTP, by IP address, on your LAN** - by default port 80, where one web server serves the Mainsail/Fluidd page *and* proxies the Moonraker API. A homelab where Moonraker sits behind a hostname-routing reverse proxy (Traefik, Caddy, Nginx Proxy Manager) or runs in Docker breaks that assumption: - Moongate talks to an **IP over plain HTTP** - no hostname, no HTTPS - so a proxy that terminates TLS on 443 and routes by hostname is never in a form Moongate can use. - The LAN address Moongate advertises comes from the printer host's own "which interface reaches the internet" lookup. Inside a Docker bridge network that returns a container-internal address (e.g. `172.x.x.x`) your phone can't reach. You don't have to route Moongate *through* your proxy - you just have to give it a plain-HTTP door straight to your Klipper web stack. Your existing `https://…` proxy hostname can stay exactly as it is; Moongate simply doesn't use it. (Remote access, when you're away from home, runs over Moongate's own tunnel and is independent of your proxy.) ### "Connection refused" right after adding the printer Adding a printer is a cloud step, so the tile appears even with no local connectivity. The first status poll then tries `http://:/server/moongate/status`, nothing is listening there, and you get connection refused. **Easiest - set the address in the app:** when adding the printer, expand **Advanced - printer on a custom network?** and enter the address you use to open its web page in a browser (e.g. `192.168.1.50:7125`). Already added it? Open the printer, tap the ✏️ icon, and set **Printer address** there. This points the app straight at your printer and skips the auto-discovery a reverse-proxy / Docker setup breaks - no server changes needed. Use an address that serves the Mainsail/Fluidd **page**, since that same origin also proxies the API - that's what fixes "Bad Gateway" below, too. **Server-side alternative** - makes the QR and auto-discovery advertise the right port for *every* device: 1. Expose your Klipper web server (the one serving Mainsail/Fluidd) on the LAN on a normal HTTP port - not only the proxy's `https://…` hostname. If it runs in Docker, publish that port to the **host's LAN**, not just the container network. 2. On the machine running Moonraker, edit (create if missing) `~/.config/moongate/config.json` and set that port: ```json { "http_port": 80 } ``` Use whatever port that web server actually listens on - `80` is the default; set e.g. `"http_port": 8080` if that's what you exposed. (In Docker this file lives under the home of the user Moonraker runs as, *inside* the container.) 3. Restart Moonraker so the plugin reloads, then **re-pair** in the app - remove the printer and add it again so the new pairing carries the right port. 4. Pair while your phone is on the **same WiFi/LAN** as the printer; initial pairing is LAN-only by design. If the address still looks wrong (the app lands on a `172.x` Docker address it can't reach), run the Moonraker container with **host networking** so it advertises your real LAN IP. ### Tile connects, but opening the printer shows "Bad Gateway" (502) The dashboard tile only uses the Moonraker **API** (`/server/…`). Tapping into a printer opens the **full Mainsail/Fluidd web page** in a built-in browser, by loading `http://:/`. A 502 there is emitted by *your* web server or proxy - the app reached that layer, but it couldn't get the page from its upstream. The usual cause: `http_port` points at **Moonraker directly (port 7125)**, which serves the API but has no web page, or at a proxy whose web-UI backend is down. `http_port` must point at the origin that serves **both** the Mainsail/Fluidd page **and** proxies the Moonraker API - the same single URL a browser uses to open your printer's interface. **Confirm it's server-side, not the app** - on a computer on the same network, open the exact URL the app uses, `http:///`, in a browser: - Bad Gateway in the browser too → it's your server/proxy config, not Moongate. Whatever serves that port can't reach its upstream (the Mainsail/Fluidd files, or Moonraker). - The interface loads fine in the browser but not in the app → [open an issue](https://github.com/PEEKYPAUL/Moongate/issues/new); that's unusual. **Fix:** point Moongate at the origin that serves the Mainsail/Fluidd page *and* proxies `/server`, `/websocket`, `/printer` to Moonraker. Quickest is in the app - open the printer, tap ✏️, and set **Printer address** to the address you open the web page at. Server-side, set `http_port` to that same port and restart Moonraker. Then re-open the printer. ## Software Update panel says Moonraker has "untracked source files" (`moongate.py`) **Symptom:** In Mainsail/Fluidd → **Settings → Software Update**, the **Moonraker** entry (not the Moongate one) carries an info badge reading `Repo has untracked source files: ['moonraker/components/moongate.py']`, sometimes with other add-ons in the same list (`timelapse.py` is a common neighbour). **Cause:** Moonraker only loads components from its own `moonraker/components/` folder, so every add-on has to put a file there, and the installer links `moongate.py` into it. Moonraker's update manager then runs `git status` inside its own checkout and lists any `.py` file git doesn't know about. It is an observation, not a fault: Moonraker still updates normally and the plugin is unaffected. **Fix:** From plugin **0.6.28** the plugin adds the line itself every time Moonraker starts, so any install picks it up on its next Moongate update (Mainsail/Fluidd → Software Update, or the app's amber plugin badge; the update restarts Moonraker). Installs made from 25 September 2026 have it from the installer as well. The line lives in git's per-clone ignore list, `~/moonraker/.git/info/exclude`, never committed and untouched by Moonraker's own updates. On an older plugin, or for another add-on's file, add the path by hand: ```bash echo "/moonraker/components/moongate.py" >> ~/moonraker/.git/info/exclude ``` Then press **Refresh** in the Software Update panel: Moonraker only re-reads its own repo when it refreshes (that button, or its own schedule, weekly by default), so the badge can outlive the fix by a while otherwise. ## Software Update panel shows an `inferred` version for Moongate **Symptom:** In Mainsail/Fluidd → **Settings → Software Update**, the Moongate entry shows something like `v0.0.0-1-gff62f74f-inferred` or a bare commit hash instead of a clean `v0.6.5`. **Cause:** The Pi was set up with an older installer that did a **shallow clone** (`git clone --depth=1`). A shallow clone carries no git tags, and Moonraker derives a component's version from tags - with none present it falls back to an inferred placeholder. This is cosmetic: one-click updates still work (Moonraker compares your commit against `origin/master`, not the tag), and the plugin itself is unaffected. **Fix:** Re-run the installer once. It detects the shallow clone and re-clones with full tag history (a small *blobless* clone), after which the panel shows a proper version: ```bash curl -fsSL https://raw.githubusercontent.com/PEEKYPAUL/Moongate/master/klipper-plugin/install.sh | bash ``` Your pairing, owner state, and device key live in `~/.config/moongate/` and are **not** touched. The old clone is preserved alongside the new one (e.g. `~/moongate.shallow-/`); you can delete it once the panel looks right. > The version in the panel tracks the **whole-project release** (the same `vX.Y.Z` as the app), so it's the number to quote when reporting a plugin issue. ## Need to capture a fresh log For mobile-side issues: ```bash # Stream logs from the running app process adb logcat --pid=$(adb shell pidof com.moongate.app.moongate) # Or filter by tag - the app uses "MOONGATE" for its own dev.log() calls adb logcat -s MOONGATE ``` For plugin-side issues: ```bash # Live tail of Moonraker (the moongate plugin logs here) journalctl -u moonraker -f # or directly: tail -f ~/printer_data/logs/moonraker.log ``` For auth-proxy issues (v0.4+): ```bash journalctl -u moongate-authproxy -f ``` For tunnel issues: ```bash journalctl -u moongate-tunnel -f tail -f /run/moongate-tunnel.log ``` ## Anything else - Read [CHANGELOG.md](CHANGELOG.md) for the version history - many issues from earlier releases have known fixes - Read [SECURITY.md](SECURITY.md) for auth / transport / threat-model questions - Open a [GitHub issue](https://github.com/PEEKYPAUL/Moongate/issues/new) with the relevant logcat / journalctl output if none of the above match