Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

17 Commits

Repository files navigation

HotBox

HotBox - a tiny enclosure heater

Contents

HotBox

100mm wide, 52mm deep and 177mm high

HotBox Technical Drawing

🔥 Disclaimer 🔥

Warning

HotBox uses a resistive heating element inside an enclosed space. Built and configured incorrectly, it is a genuine fire risk.

This project is shared as-is for experienced makers who understand the risks. The author accepts no responsibility for damage, injury, or loss of any kind. You build and run this entirely at your own risk.

If you're not comfortable working with high-current DC systems and thermal management, please don't attempt this build.

BOM

BOM

HotBox.v18.mp4

#ImagePartDescriptionQtyNotesLink
1PTC HeaterPTC Heater Element24V 200W ceramic PTC heater1Self-limiting; derate above 87°Camazon.co.uk
2Thermal FuseSquare Thermal Fuse125°C Ceramic Thermal 250V 15A1CQC rated; soldered inlineamazon.co.uk
34020 FanAxial Fan24V 4020 axial fan2Must run at 100% when element is poweredamazon.co.uk
4NTC 100K thermistorThermistorNTC 100K1Seats in dedicated hole in PTC body; use thermal glueamazon.co.uk
5Thermal glueThermal GlueEC360 2W/mK thermal adhesive1Bonds thermistor to PTC element bodyamazon.co.uk
6M3 SHCSM3×10mm SHCSSocket head cap screw2Secures PTC heater element
7M3 SHCSM3×25mm SHCSSocket head cap screw2Heater case to fan case
8M3 SHCSM3×30mm SHCSSocket head cap screw4Fans through to fan duct
9Heat-Set InsertHeat-Set InsertM3×4mm brass heat-set insert8amazon.co.uk
103x3mm MagnetsMagnets3×3mm N42 neodymium magnets8For lid retentionamazon.co.uk
11WAGO 221-412WAGO 221-4122-conductor lever-nut splicing connector3Tool-free wire connections for heater, fans and thermistoramazon.co.uk
12LidLidPrinted part1Print in ABS/ASASTL
13Heater CaseHeater CasePrinted part1Print in ABS/ASASTL
14Fan CaseFan CasePrinted part1Print in ABS/ASASTL
15Fan DuctFan DuctPrinted part1Print in ABS/ASASTL
16Fan Duct ZeroG NebulaFan Duct ZeroG NebulaPrinted part1Print in ABS/ASA; alternative duct for ZeroG Nebula enclosureSTL

Note

ZeroG-Nebula

I made this for me on my ZeroG Nebula printer. I've provided a fan duct without the filler plate which will bolt onto a 2020 extrusion, but I'm not going to try and build mounting solutions for other printers without being able to test or measure.

If you want to use this heater on your printer, either:

  • Modify the fan duct, test it, let me know and it can be added to the repo
  • spec out what you need and I'll build it, (untested) and publish it

Installation instructions

(Pictures to follow soon)

Main body

  • Fit all heat-set inserts into the correct holes with a soldering iron
  • Trim the ends of the thermal fuse so that the wires fit neatly into the wagos without touching the lid
  • Cut one side of the PTC thermal heater wire to length so that it fits in the wag opposite the cable hole without pushing the lid
  • Fill the round hole in the PTC heater with thermal glue and insert the thermistor and wait to DRY (this will not cure until up to temperature later)
  • Slot the WAGO's and thermal fuse into the correct holes
  • Feed the wires through the cable hole. This will be SUPER TIGHT! The hole is as large as it can be whilst maintaining the small size of the heater
  • Before pulling all the wires completely through, put the wired end of the PTC heater under the thermal fuse holder and then slot in the front. Wiggle it a bit and get it into position
  • Screw down the PTC heater
  • Pull the wires through so that they are neat and not obstructing the heater
  • Push the wires through the fan case and down through the middle hole, but do not tighten
  • Put the fans in the right position in the fan case - airflow going up into the PTC element
  • Feed the fan wires through the middle hole
  • Feed everything through the middle hole of the fan duct (you may need to join the PTC wire to a new piece of wire and shield it)
  • Screw the fan case to the fan duct
  • Keeping the wires tight with one hand push it all together
  • Screw the heater case to the fan case
  • Use a drop of superglue in the magnet holes and tap/push/force them in with something hard

Electronics

HotBox Wiring Diagram

This is very much dependent on your setup, but I did the following:

  • Used WAGO's on the printer body to connect everything together to keep it neat
  • Used a Mosfet running off the PSU and a fan PIN to modulate it
  • Connected the thermistor and fans to the main board
  • Configured everything in Klipper (see below)

Klipper config

printer.cfg

# ============================================================# HOTBOX Config# ============================================================[heater_generic chamber]
heater_pin: PA3 # <--------------- change this
sensor_type: Generic 3950
sensor_pin: PF6 # <--------------- change this
min_temp: 0
max_temp: 70
control: pid
pwm_cycle_time: 0.01
pid_Kp: 100
pid_Ki: 0.3
pid_Kd: 30
[verify_heater chamber]
max_error: 600
check_gain_time: 300
hysteresis: 5
heating_gain: 0.5
# Fans will run at 100% any time the hotbox element exceeds 35, regardless# of what Klipper is doing (PID calibration, printing, idle, anything).[temperature_fan hotbox]
pin: PD14 # <--------------- change this
sensor_type: Generic 3950
sensor_pin: PF7 # <--------------- change this
min_temp: 0
max_temp: 120
target_temp: 35.0
min_speed: 0.0
max_speed: 1.0
control: watermark
max_delta: 1
kick_start_time: 0.5
# ============================================================# HOTBOX CONTROL LOOP# Runs every 5 seconds from printer startup# Stores chamber target into HOTBOX_STATE so backoff can restart# Fan speed is now managed automatically by [temperature_fan hotbox]# This loop only handles: state tracking + overtemp backoff# ============================================================[delayed_gcode HOTBOX_CONTROL]
initial_duration: 5
gcode:
{% set hotbox_temp = printer['temperature_fan hotbox'].temperature %}
{% set chamber_target = printer['heater_generic chamber'].target %}
{% set heater_on = chamber_target > 0 %}
{% if heater_on %}
SET_GCODE_VARIABLE MACRO=HOTBOX_STATE VARIABLE=target VALUE={chamber_target}
{% endif %}
{% if hotbox_temp > 87 and heater_on %}
SET_HEATER_TEMPERATURE HEATER=chamber TARGET=0
UPDATE_DELAYED_GCODE ID=HOTBOX_BACKOFF DURATION=60
RESPOND MSG="HotBox: Element at {hotbox_temp}C - 60s backoff, will restart automatically"
{% endif %}
UPDATE_DELAYED_GCODE ID=HOTBOX_CONTROL DURATION=5
# ============================================================# HOTBOX BACKOFF TIMER# Triggered by HOTBOX_CONTROL after element overtemp# Calls HOTBOX_RESTART after 60 seconds# ============================================================[delayed_gcode HOTBOX_BACKOFF]
initial_duration: 0
gcode:
HOTBOX_RESTART
[include hotbox_macros.cfg]# <--------------- put this with your other includes or at the end of the file

hotbox_macros.cfg

# ============================================================# hotbox_macros.cfg# HotBox enclosure heater user macros# Include this file from printer.cfg with [include hotbox_macros.cfg]# ============================================================# ============================================================# HOTBOX_STATE# Stores the intended chamber target so backoff can restart# This is set both by HOTBOX_ON and by the HOTBOX_CONTROL loop# so it works whether you use the macro or set temp directly in Mainsail# ============================================================[gcode_macro HOTBOX_STATE]
variable_target: 0
gcode:
# ============================================================# HOTBOX_ON# Usage: HOTBOX_ON TARGET=60# Heats enclosure to target temperature (default 60C, max 70C)# Fans start automatically via [temperature_fan hotbox] - no manual control needed# ============================================================[gcode_macro HOTBOX_ON]
description: Heat enclosure to target temperature. Usage: HOTBOX_ON TARGET=60
gcode:
{% set TARGET = params.TARGET|default(60)|float %}
{% if TARGET > 70 %}
RESPOND MSG="HotBox: Target {TARGET}C exceeds safe maximum of 70C - aborting"
{% elif TARGET < 30 %}
RESPOND MSG="HotBox: Target {TARGET}C is too low - minimum is 30C"
{% else %}
SET_GCODE_VARIABLE MACRO=HOTBOX_STATE VARIABLE=target VALUE={TARGET}
SET_HEATER_TEMPERATURE HEATER=chamber TARGET={TARGET}
UPDATE_DELAYED_GCODE ID=HOTBOX_CONTROL DURATION=5
RESPOND MSG="HotBox: Heating enclosure to {TARGET}C - fans will run automatically"
{% endif %}
# ============================================================# HOTBOX_OFF# Turns heater off and clears stored target# Fans continue automatically until hotbox element cools below 40C# ============================================================[gcode_macro HOTBOX_OFF]
description: Turn off HotBox heater. Fans run automatically until hotbox cools below 40C
gcode:
SET_GCODE_VARIABLE MACRO=HOTBOX_STATE VARIABLE=target VALUE=0
SET_HEATER_TEMPERATURE HEATER=chamber TARGET=0
RESPOND MSG="HotBox: Heater off - fans will run automatically until hotbox cools below 40C"# ============================================================# HOTBOX_STATUS# Prints current hotbox status to console# ============================================================[gcode_macro HOTBOX_STATUS]
description: Report current HotBox temperatures and fan status
gcode:
{% set hotbox_temp = printer['temperature_fan hotbox'].temperature %}
{% set chamber_temp = printer['heater_generic chamber'].temperature %}
{% set chamber_target = printer['heater_generic chamber'].target %}
{% set fan_speed = printer['temperature_fan hotbox'].speed %}
{% set intended_target = printer['gcode_macro HOTBOX_STATE'].target %}
RESPOND MSG="HotBox Status:"
RESPOND MSG=" Chamber temp: {chamber_temp}C / Target: {chamber_target}C"
RESPOND MSG=" Hotbox element: {hotbox_temp}C"
RESPOND MSG=" Fan speed: {(fan_speed * 100)|round}%"
RESPOND MSG=" Stored target: {intended_target}C"# ============================================================# HOTBOX_RESTART# Called automatically after 60s backoff - do not call manually# Restarts heater using stored target if element has cooled below 75C# ============================================================[gcode_macro HOTBOX_RESTART]
description: Internal - restarts heater after backoff cooldown
gcode:
{% set intended_target = printer['gcode_macro HOTBOX_STATE'].target %}
{% set hotbox_temp = printer['temperature_fan hotbox'].temperature %}
{% if intended_target > 0 %}
{% if hotbox_temp < 70 %}
SET_HEATER_TEMPERATURE HEATER=chamber TARGET={intended_target}
RESPOND MSG="HotBox: Element cooled to {hotbox_temp}C - restarting heater to {intended_target}C"
{% else %}
RESPOND MSG="HotBox: Element still at {hotbox_temp}C - waiting another 60s"
UPDATE_DELAYED_GCODE ID=HOTBOX_BACKOFF DURATION=60
{% endif %}
{% else %}
RESPOND MSG="HotBox: No stored target - not restarting"
{% endif %}

Gotchas

  • PID calibration may not work close to the enclosure's thermal limit. If the enclosure cannot reliably reach and re-reach the target temperature through multiple oscillation cycles, the calibration will fail. Try a lower target temperature (e.g. TARGET=50 instead of TARGET=60).

  • PTC heater elements are self-limiting — they reduce power output as they approach their rated temperature. This makes PID calibration difficult as the element may throttle itself during the calibration oscillation cycles.

  • Do NOT run PID_CALIBRATE with the fans configured as [fan_generic] controlled via delayed_gcode macros. Klipper holds the gcode queue during PID_CALIBRATE, which prevents delayed_gcode from executing, meaning fans will not run. This WILL cause the element to overheat. The fans MUST be configured as [temperature_fan] so they operate independently of the gcode queue.

  • The hotbox fans must run at 100% whenever the PTC element is receiving power. This is not optional — the element will overheat and blow the thermal fuse without active cooling.

  • Always let the fans cool the element down to 50 degrees or below before switching the machine off.

  • There is a big thermal delay between the PTC element and the thermistor due to the distance between the two. Use thermal glue to bridge the gap, but factor this delay into any calculations.

  • Running the PTC heater above 87°C for extended periods risks triggering the 125°C thermal fuse. The HOTBOX_CONTROL overtemp cutoff should be set to 87°C maximum.

  • The thermistor must be seated directly in the dedicated hole in the PTC element body. Poor contact will cause it to read significantly below actual element temperature, meaning the emergency stop and overtemp cutoff will not trigger in time to protect the thermal fuse.

  • The [temperature_fan] target is user-editable in Mainsail/Fluidd. Setting it above the element's safe operating temperature will prevent fans from running at the correct threshold. Do not modify this value.

  • Klipper's max_temp on the [temperature_fan] section is an emergency shutdown threshold, not a cap on the settable target. Setting it too low will cause Klipper to hard-stop during normal operation.

  • There needs to be enough overhead in the PSU to power a 200W PTC heater on the 24v rail, do the maths and ensure that there's enough power. If not, run an additional 200W DIN rail PSU or upgrade

  • Do NOT use control: watermark for the chamber heater. Bang-bang switching of the heating element causes hard current transients on the shared 24V rail which affect TMC stepper driver behaviour, producing Z-axis banding artefacts on prints. Always use control: pid with pwm_cycle_time: 0.01 to smooth current draw to a near-constant average.

Shameless promotions

Password Manager Reviews

VPN Reviews

AI Image Generator Reviews

Inspiration and credit

  • BentoBox - I LOVE this and it was what triggered the idea for this design. Simple but effective in small spaces
  • BentoBox Fan Duct for ZeroG - This is an absolutely great idea and I used the theory to build a similar integration for HotBox

About

HotBox - tiny enclosure heater

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors