-
Notifications
You must be signed in to change notification settings - Fork 0
OctoPrint Plugin Setup
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-daemonshowsactive)- You can SSH into the Pi
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 |
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/cssFrom 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/find ~/.octoprint/plugins/enclosure_proxy -type fExpected 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
sudo systemctl restart octoprinttail -n 100 ~/.octoprint/logs/octoprint.log | grep -i enclosureYou 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.
Once the plugin is loaded, test the endpoints from the Pi using curl. Your API key is found at OctoPrint → Settings → Application Keys.
curl -s http://127.0.0.1/api/plugin/enclosure_proxy \
-H "X-Api-Key: YOUR_API_KEY" ; echoExpected 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": ""
}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"}' ; echocurl -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}' ; echocurl -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}' ; echocurl -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}' ; echoAll 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.
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.
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.
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.
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.
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.
The frontend (enclosure_proxy.js) is an OctoPrint ViewModel registered with OCTOPRINT_VIEWMODELS.
- Polls
GET /api/plugin/enclosure_proxyevery 3 seconds. -
startPolling()always callsstopPolling()first before setting a new interval. This prevents multiple polling loops from stacking up if the page is refreshed or OctoPrint reinitialises the UI. -
currentModeis initialised tonull. This ensures the very first status response always triggers a full UI render, even if the ESP32 is already inOFFmode (which would otherwise look like "no change"). - When the browser tab is hidden (user switches tabs), polling pauses via the
visibilitychangeAPI. It resumes when the tab is brought back into focus. This reduces unnecessary load on the Pi.
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.
If the enclosure temperature exceeds 60°C, the ESP32 engages a hardware safety:
- Heater is shut off immediately
- Exhaust fan is forced to 100%
-
SAFETYfield in status becomes1 - 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:
- Identify and fix the root cause of the overtemperature condition.
- 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 activeOr 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- Once
SAFETYreturns to0in the status, AUTO mode can be re-enabled.
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.
OctoPrint didn't load the plugin. Check the log:
tail -n 100 ~/.octoprint/logs/octoprint.log | grep -i enclosureCommon 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 -pcommands from Step 1
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.
The plugin is running but crashing at request time. Check the log:
tail -f ~/.octoprint/logs/octoprint.logThen trigger a request and watch for the traceback.
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 ; echoIf the daemon is down, see the Pi Daemon Setup page.
The daemon is running but has lost contact with the ESP32. Check:
sudo journalctl -u enclosure-daemon -f
ls /dev/ttyUSB0Try unplugging and replugging the ESP32 USB cable. The daemon reconnects automatically.
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 ; echoA slow response (>1 second) means the daemon is struggling with serial communication — see the Pi Daemon Setup troubleshooting section.
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.