Skip to content

OctoPrint Plugin Setup

Dilf edited this page Feb 17, 2026 · 2 revisions

The OctoPrint plugin adds an Enclosure Control panel to the OctoPrint sidebar. It proxies commands from the browser through OctoPrint to the Pi daemon, which forwards them to the ESP32 over USB serial.

Prerequisites

  • OctoPrint is installed and running on the Pi (OctoPi image recommended)
  • The Pi daemon is installed and running (sudo systemctl status enclosure-daemon shows active)
  • You can SSH into the Pi

How It Works

The plugin sits between the browser and the daemon. It never touches the serial port directly.

Browser
   │  GET/POST  /api/plugin/enclosure_proxy
   ▼
OctoPrint Plugin  (__init__.py)
   │  HTTP  →  127.0.0.1:8070
   ▼
Pi Daemon  (enclosure_daemon.py)
   │  USB Serial  →  /dev/ttyUSB0
   ▼
ESP32

The plugin is made up of four files:

File Purpose
__init__.py Core plugin logic — registers API endpoints, proxies requests to the daemon
templates/enclosure_proxy_sidebar.jinja2 HTML injected into the OctoPrint sidebar
static/js/enclosure_proxy.js Frontend polling, button/slider handlers, status display
static/css/enclosure_proxy.css Sidebar styling

Installation

Step 1 — Create the plugin folder structure

SSH into the Pi and create the required directories:

mkdir -p ~/.octoprint/plugins/enclosure_proxy/templates
mkdir -p ~/.octoprint/plugins/enclosure_proxy/static/js
mkdir -p ~/.octoprint/plugins/enclosure_proxy/static/css

Step 2 — Copy the plugin files

From your development machine (with the repo cloned), run:

scp octoprint-plugin/enclosure_proxy/__init__.py \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/

scp octoprint-plugin/enclosure_proxy/templates/enclosure_proxy_sidebar.jinja2 \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/templates/

scp octoprint-plugin/enclosure_proxy/static/js/enclosure_proxy.js \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/static/js/

scp octoprint-plugin/enclosure_proxy/static/css/enclosure_proxy.css \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/static/css/

Or if you have the repo cloned on the Pi already:

cp -r ~/HotBox/octoprint-plugin/enclosure_proxy \
      ~/.octoprint/plugins/

Step 3 — Verify the file structure

find ~/.octoprint/plugins/enclosure_proxy -type f

Expected output:

/home/pi/.octoprint/plugins/enclosure_proxy/__init__.py
/home/pi/.octoprint/plugins/enclosure_proxy/templates/enclosure_proxy_sidebar.jinja2
/home/pi/.octoprint/plugins/enclosure_proxy/static/js/enclosure_proxy.js
/home/pi/.octoprint/plugins/enclosure_proxy/static/css/enclosure_proxy.css

Step 4 — Restart OctoPrint

sudo systemctl restart octoprint

Step 5 — Confirm the plugin loaded

tail -n 100 ~/.octoprint/logs/octoprint.log | grep -i enclosure

You should see lines like:

octoprint.plugin.core - INFO - Enclosure Control = /home/pi/.octoprint/plugins/enclosure_proxy
octoprint.plugins.enclosure_proxy - INFO - EnclosureProxyPlugin started

If you see a Python traceback instead, see the Troubleshooting section below.


Verifying the API

Once the plugin is loaded, test the endpoints from the Pi using curl. Your API key is found at OctoPrint → Settings → Application Keys.

GET status

curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
  -H "X-Api-Key: YOUR_API_KEY" ; echo

Expected response:

{
  "ok": true,
  "parsed": {
    "TEMP": "24.60",
    "RPM": "0",
    "HEATER": "0.0",
    "EXHAUST": "0.0",
    "SETPOINT": "25.0",
    "MODE": "OFF",
    "CONTROL": "0",
    "SAFETY": "0"
  },
  "raw": "@12 OK TEMP 24.60 RPM 0 ...",
  "ts": 1771245866.52,
  "error": ""
}

POST — change mode

curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"mode","mode":"MANUAL"}' ; echo

POST — set temperature setpoint

curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"setpoint","c":35}' ; echo

POST — manual heater control

curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"heater","value":30}' ; echo

POST — manual exhaust control

curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":"exhaust","value":80}' ; echo

API Reference

All requests go to the same endpoint: POST /api/plugin/enclosure_proxy

The command field in the request body determines what happens.

Command Required fields What it does
setpoint c (float, 10–50) Sets the PID target temperature in °C
mode mode (string) Sets control mode: OFF, AUTO, or MANUAL
heater value (float, 0–100) Sets heater duty cycle. MANUAL mode only.
exhaust value (float, 0–100) Sets exhaust fan duty cycle. MANUAL mode only.

GET /api/plugin/enclosure_proxy — returns the current cached status from the daemon.


Sidebar UI

Once the plugin is loaded, an Enclosure Control panel appears in the OctoPrint sidebar (left column of the web UI). It updates every 3 seconds.

Status section

Always visible regardless of mode. Shows live readings from the ESP32:

Field Description
Temperature Current enclosure temp in °C from the BME280 sensor
RPM Exhaust fan speed. Shows 0 when fan is off.
Mode Current control mode: OFF, AUTO, or MANUAL

If the daemon loses contact with the ESP32, all fields display NO SIGNAL.

Control Mode buttons

Three buttons: OFF, AUTO, MANUAL. The active mode is highlighted. Clicking a button sends the mode change immediately to the ESP32 via the daemon.

  • OFF — shuts down all outputs. Safe idle state.
  • AUTO — enables the PID controller. The ESP32 automatically drives the heater or exhaust fan to reach the setpoint. The setpoint input appears (see below).
  • MANUAL — disables the PID. Heater and exhaust sliders appear (see below). Entering MANUAL resets both outputs to 0% as a known safe starting point.

⚠️ AUTO mode cannot be entered if a safety fault is active (SAFETY: 1). The ESP32 will reject the command. See Safety Faults below.

AUTO mode — Setpoint control

Visible only in AUTO mode. Enter a target temperature between 10°C and 50°C and click Set Setpoint. The PID controller will drive the enclosure toward this temperature automatically.

The displayed setpoint value is kept in sync with the ESP32 — if it was changed externally (e.g. via curl), the UI will reflect the current value on the next poll.

MANUAL mode — Heater and Exhaust sliders

Visible only in MANUAL mode. Two range sliders, each 0–100% in steps of 5.

  • The percentage label updates live as you drag the slider.
  • The command is sent to the ESP32 only when you release the slider — this prevents flooding the serial port with commands while dragging.
  • Both outputs hold their last set value until you change them or switch mode.

How the JavaScript Works

The frontend (enclosure_proxy.js) is an OctoPrint ViewModel registered with OCTOPRINT_VIEWMODELS.

Polling

  • Polls GET /api/plugin/enclosure_proxy every 3 seconds.
  • startPolling() always calls stopPolling() first before setting a new interval. This prevents multiple polling loops from stacking up if the page is refreshed or OctoPrint reinitialises the UI.
  • currentMode is initialised to null. This ensures the very first status response always triggers a full UI render, even if the ESP32 is already in OFF mode (which would otherwise look like "no change").
  • When the browser tab is hidden (user switches tabs), polling pauses via the visibilitychange API. It resumes when the tab is brought back into focus. This reduces unnecessary load on the Pi.

Slider behaviour

Sliders use two separate event listeners:

  • input — fires continuously while dragging, updates the % label only.
  • change — fires once on release, sends the actual API command.

This keeps the UI feeling responsive while avoiding serial command floods.


Safety Faults

If the enclosure temperature exceeds 60°C, the ESP32 engages a hardware safety:

  • Heater is shut off immediately
  • Exhaust fan is forced to 100%
  • SAFETY field in status becomes 1
  • AUTO mode is locked out

The sidebar will show MODE: OFF and the AUTO button will appear to do nothing (the ESP32 silently rejects the command).

To recover:

  1. Identify and fix the root cause of the overtemperature condition.
  2. Send a fault reset — either via curl:
curl -s http://127.0.0.1:8070/mode \
  -H "Content-Type: application/json" \
  -d '{"mode":"AUTO"}' ; echo
# Will return ERR FAULT RESET_REQUIRED if fault is still active

Or directly to the daemon:

curl -s -X POST http://127.0.0.1:8070/mode \
  -H "Content-Type: application/json" \
  -d '{"mode":"RESET_FAULT"}' ; echo
  1. Once SAFETY returns to 0 in the status, AUTO mode can be re-enabled.

Updating the Plugin

After making changes to any plugin file on your dev machine:

# Copy changed files to the Pi
scp octoprint-plugin/enclosure_proxy/__init__.py \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/

scp octoprint-plugin/enclosure_proxy/static/js/enclosure_proxy.js \
    pi@octopi.local:/home/pi/.octoprint/plugins/enclosure_proxy/static/js/

# Always restart OctoPrint after plugin changes
ssh pi@octopi.local "sudo systemctl restart octoprint"

Changes to the JavaScript or CSS take effect after OctoPrint restarts and you hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R) to clear the cached assets.


Troubleshooting

Sidebar panel not appearing

OctoPrint didn't load the plugin. Check the log:

tail -n 100 ~/.octoprint/logs/octoprint.log | grep -i enclosure

Common causes:

  • Syntax error in __init__.py — look for a Python traceback in the log
  • Wrong file permissions — run chmod 644 ~/.octoprint/plugins/enclosure_proxy/__init__.py
  • Missing folder structure — re-run the mkdir -p commands from Step 1

API returns 404

The plugin loaded but the route isn't registered. This usually means __init__.py has an import error that prevented the class from being defined. Check the log for tracebacks.

API returns 500

The plugin is running but crashing at request time. Check the log:

tail -f ~/.octoprint/logs/octoprint.log

Then trigger a request and watch for the traceback.

API returns 502

Plugin is working but can't reach the daemon on port 8070.

sudo systemctl status enclosure-daemon
curl -s http://127.0.0.1:8070/status ; echo

If the daemon is down, see the Pi Daemon Setup page.

Status shows NO SIGNAL in the sidebar

The daemon is running but has lost contact with the ESP32. Check:

sudo journalctl -u enclosure-daemon -f
ls /dev/ttyUSB0

Try unplugging and replugging the ESP32 USB cable. The daemon reconnects automatically.

Sliders jump back when I release them

This happens if the status poll fires immediately after a slider command and overwrites the slider value with the (not-yet-updated) cached status. The 3-second poll interval is intentionally longer than the serial round-trip time (~500ms) to avoid this. If it still happens, check that the daemon is responding quickly by running:

curl -s http://127.0.0.1:8070/status ; echo

A slow response (>1 second) means the daemon is struggling with serial communication — see the Pi Daemon Setup troubleshooting section.

Plugin version showing as ? in OctoPrint tracking logs

This is normal — the ? appears because the plugin isn't installed through the OctoPrint plugin manager (it's a manually dropped-in folder). It has no effect on functionality.