-
Notifications
You must be signed in to change notification settings - Fork 0
Daemon Setup On PI
The Pi daemon (enclosure_daemon.py) is a Flask HTTP server that runs permanently in the background on the Raspberry Pi. It owns the USB serial connection to the ESP32 exclusively, and exposes a local HTTP API on port 8070 that the OctoPrint plugin talks to.
Prerequisites
- Raspberry Pi running OctoPi with SSH enabled
- ESP32 flashed with the HotBox firmware and connected via USB
- The repo cloned on your development machine
The daemon is the middleman between OctoPrint and the ESP32. Nothing else talks to the serial port directly.
OctoPrint Plugin
│ HTTP → 127.0.0.1:8070
▼
Pi Daemon (enclosure_daemon.py)
│ USB Serial → /dev/ttyUSB0 @ 115200 baud
▼
ESP32
-
connect_serial()opens/dev/ttyUSB0and sendsPINGcommands until the ESP32 responds. Retries indefinitely — this handles the Pi booting faster than the ESP32 initialises. - A background thread starts running
poller(), which sendsGET STATUSto the ESP32 every 2 seconds and caches the result. - The Flask HTTP server starts on
127.0.0.1:8070and begins accepting requests.
Rather than hitting the serial port on every HTTP request (slow, and risks contention), the poller keeps a fresh _last_status snapshot in memory. When the OctoPrint plugin calls GET /status, the cached value is returned instantly — no serial I/O required.
A threading.Lock() wraps every serial read/write. This prevents the background poller and an incoming HTTP request from both trying to write to the serial port simultaneously, which would corrupt the data stream.
If the serial port drops (ESP32 reset, USB cable glitch), the daemon:
- Detects the failure on the next write or read attempt
- Forcefully closes and discards the broken port handle
- Backs off for 3 seconds before retrying
- Reopens
/dev/ttyUSB0automatically on the next command
No reboot required — the daemon is self-healing.
Run this on your computer:
ssh pi@octopi.local
Enter the Pi's password when prompted (default: raspberry unless changed).
On the Pi, create a dedicated folder and an isolated Python environment for the daemon:
mkdir -p ~/enclosure_daemon
python3 -m venv ~/enclosure_daemon/venv
Why a virtual environment?
OctoPi manages its own Python environment separately. Installing packages into the system Python risks breaking OctoPrint. A venv keeps the daemon's dependencies (flask, pyserial) fully isolated.
Activate the venv and install the required packages:
source ~/enclosure_daemon/venv/bin/activate
pip install --upgrade pip
pip install flask pyserial
deactivate
Verify the packages installed correctly:
~/enclosure_daemon/venv/bin/pip list | grep -E "Flask|pyserial"
Expected output:
Flask 3.x.x
pyserial 3.x.x
Option A — Recommended: copy from your computer via scp
Run this on your computer (not inside SSH):
scp daemon/enclosure_daemon.py pi@octopi.local:/home/pi/enclosure_daemon/
Option B — Edit directly on the Pi
If you don't have the repo on your computer, SSH in and create the file manually:
nano ~/enclosure_daemon/enclosure_daemon.py
In nano:
- Paste the daemon code
-
Ctrl+OthenEnterto save -
Ctrl+Xto exit
ls -lh ~/enclosure_daemon/
Expected output:
drwxr-xr-x enclosure_daemon/
├── enclosure_daemon.py
└── venv/
Before setting it up as a service, do a quick sanity check by running it directly. Make sure the ESP32 is plugged into the Pi via USB first.
source ~/enclosure_daemon/venv/bin/activate
python3 ~/enclosure_daemon/enclosure_daemon.py
You should see:
[daemon] Waiting for ESP32...
[serial] Opened /dev/ttyUSB0
[daemon] Connected to /dev/ttyUSB0 at 115200
[daemon] ESP responded: @1 OK PONG
Enclosure daemon running on port 8070...
* Running on http://127.0.0.1:8070
[poll] reply: @2 OK TEMP 24.60 RPM 0 HEATER 0.0 ...
With the daemon still running, open a second SSH session and test the HTTP API:
curl -s http://127.0.0.1:8070/status ; 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": "@2 OK TEMP 24.60 RPM 0 ...",
"ts": 1771245866.52,
"error": ""
}
If this works, press Ctrl+C to stop the manual run and proceed to the systemd setup below.
If /dev/ttyUSB0 not found:
ls /dev/ttyUSB*
If nothing shows, the ESP32 isn't being detected. Try a different USB cable or port, or check that the CP210x/CH340 driver is available on the Pi.
Running the daemon manually only lasts until you close the SSH session. The systemd service makes it run permanently in the background and restart automatically if it ever crashes.
From your computer (not inside SSH):
scp systemd/enclosure-daemon.service pi@octopi.local:/tmp/
SSH into the Pi if you aren't already connected:
ssh pi@octopi.local
Move the file into the systemd directory (requires sudo):
sudo mv /tmp/enclosure-daemon.service /etc/systemd/system/
Verify it landed correctly:
cat /etc/systemd/system/enclosure-daemon.service
You should see the full service file contents with the [Unit], [Service], and [Install] sections.
After adding any new service file, systemd must be told to re-read its configuration:
sudo systemctl daemon-reload
Always run this after creating or editing a
.servicefile — without it, systemd uses the old (or non-existent) version.
Enable the daemon so it starts automatically on every boot:
sudo systemctl enable enclosure-daemon
Expected output:
Created symlink /etc/systemd/system/multi-user.target.wants/enclosure-daemon.service
→ /etc/systemd/system/enclosure-daemon.service.
Enable only registers it for future boots. Start it immediately with:
sudo systemctl start enclosure-daemon
sudo systemctl status enclosure-daemon --no-pager
You should see:
● enclosure-daemon.service - ESP32 Enclosure Daemon
Loaded: loaded (/etc/systemd/system/enclosure-daemon.service; enabled)
Active: active (running) since ...
Main PID: XXXX (python)
The key lines to check:
-
Active: active (running)— daemon is up ✅ -
enabledin the Loaded line — will start on next boot ✅
If you see failed or inactive, jump to Troubleshooting below.
Watch the live log output for a few seconds:
sudo journalctl -u enclosure-daemon -f
You should see [poll] reply: lines arriving every 2 seconds:
[daemon] Waiting for ESP32...
[serial] Opened /dev/ttyUSB0
[daemon] ESP responded: @1 OK PONG
Enclosure daemon running on port 8070...
[poll] reply: @2 OK TEMP 24.60 RPM 0 HEATER 0.0 EXHAUST 0.0 ...
[poll] reply: @3 OK TEMP 24.61 RPM 0 HEATER 0.0 EXHAUST 0.0 ...
Press Ctrl+C to exit the log view.
Confirm the HTTP API is working end-to-end:
# Status (uses cached poller data — no serial I/O)
curl -s http://127.0.0.1:8070/status ; echo
Ping (hits the ESP32 live)
curl -s http://127.0.0.1:8070/ping ; echo
Change mode to MANUAL
curl -s -X POST http://127.0.0.1:8070/mode
-H "Content-Type: application/json"
-d '{"mode":"MANUAL"}' ; echo
Set heater to 30%
curl -s -X POST http://127.0.0.1:8070/heater
-H "Content-Type: application/json"
-d '{"value":30}' ; echo
Set exhaust to 50%
curl -s -X POST http://127.0.0.1:8070/exhaust
-H "Content-Type: application/json"
-d '{"value":50}' ; echo
Set target temperature (only active in AUTO mode)
curl -s -X POST http://127.0.0.1:8070/setpoint
-H "Content-Type: application/json"
-d '{"c":35}' ; echo
All of these should return JSON with "ok": true.
All endpoints are on 127.0.0.1:8070 — localhost only, not accessible from the network directly.
| Method | Endpoint | Body | Description |
|---|---|---|---|
| GET | /ping | — | Sends PING to ESP32 live, returns PONG reply |
| GET | /status | — | Returns cached status snapshot (updated every 2s by poller) |
| POST | /setpoint | {"c": 35.0} | Sets PID target temperature in °C (range: 10–50) |
| POST | /mode | {"mode": "AUTO"} | Sets control mode: OFF, AUTO, or MANUAL |
| POST | /heater | {"value": 30.0} | Sets heater duty 0–100%. MANUAL mode only. |
| POST | /exhaust | {"value": 80.0} | Sets exhaust fan duty 0–100%. MANUAL mode only. |
The daemon is running but lost contact with the ESP32. Check the live log:
sudo journalctl -u enclosure-daemon -f
You'll see one of:
-
[serial] Write error ... closing port— USB connection dropped. Try unplugging and replugging the ESP32. The daemon reconnects automatically. -
[poll] No reply— ESP32 is connected but not responding. May have crashed or be mid-reboot. Wait a few seconds — it will recover.
ls /dev/ttyUSB* /dev/ttyACM*
If nothing appears, the Pi isn't detecting the ESP32 at all:
- Try a different USB cable (data cable, not charge-only)
- Try a different USB port on the Pi
- Check
dmesg | tail -n 20for USB device detection messages
[poll] reply: None
[poll] reply: None
The ESP32 is connected but not responding to GET STATUS. Possible causes:
- Firmware hasn't finished booting yet — wait 5–10 seconds
- Wrong baud rate — confirm
BAUD = 115200in the daemon matches the firmware'sSerial.begin(115200) - ESP32 is in a fault state — connect to it directly via the Arduino Serial Monitor and send
PINGmanually to check
# Check group membership
groups pi
Add pi to dialout group if missing
sudo usermod -aG dialout pi
Reboot for the group change to take effect
sudo reboot