Stephen Harrison
Published © CC BY-NC-SA

Humidity Controlled 3D Printer Filament Storage

Keep your 3D printer filaments in top condition with this really useful dehumidifying and internet connected storage box.

AdvancedFull instructions provided8 hours7,260

Things used in this project

Hardware components

Photon
Particle Photon
×1
5mm Acrylic Sheet
×1
40mm 3 Pin PC Fan
×1
40mm Fan Filter
×1
Really Useful Storage Box 19XL
Size is important for this. It needs the deep lid and to be A4 paper size base for the spools to fit into. You can however go bigger if you prefer but other boxes have very poor lid/base seal and let air in badly.
×1
12V DC Power Supply
×1
Humidity Monitor and Fan Controller PCB
×1
Draft Excluder
Used to improve the lid seal to keep the nice dry air inside. Not needed for the smaller lid.
×1
Silica Gel (2-5mm bead, self indicating)
You need about 750g for the cartridge, get 2x that to allow for a change of gel whilst the other is drying (and also some waste as it escapes and goes everywhere!)
×1
Tensol-12
Acrylic glue
×1
M4 Machine Screws (70mm)
×1
M4 Wing Nuts
×1

Software apps and online services

Tinamous.com
Monitor the humidity of your filament and override the fan to give it a boost.
MakerCase

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)
3D Printer (generic)
3D Printer (generic)
Only needed for the Pi Handles if you want them.

Story

Read more

Custom parts and enclosures

Funnel 3 - Use this one!

The best option for filling the silica gel

Funnel 2 - Alternative

An alternative funnel if you prefer this.

Funnel 1 - Another alternative

An alternative funnel

Main acrylic case

Layed out for a 600x400 acrylic sheet.

Fan Insert

Laser cut insert for the case to hold a 40mm fan.

Pi Handle

3D Print this if you want the Pi Handle

Schematics

Eagle Schematic

Eagle PCB

Schematic

PCB - All Layers

Code

Humidity Monitor (V4)

Arduino
Paste this into the Particle Build IDE. Be sure to add the HTU21D library as well.
// This #include statement was automatically added by the Particle IDE.
#include <HTU21D.h>

// Particle Product information.
PRODUCT_ID(5334);
PRODUCT_VERSION(2); 

// **********************************************************************
// Filament storage humidity monitor V2.04 PCB (Traffic lights version).
// **********************************************************************

int fanPulseIn = D4;
int fanControl  = D3;
int buzzer = DAC;
int highHumidityLed = A3;
int fanLed = A4;
int oshLed = A5;

// Temperature and humidity
HTU21D humiditySensor;

// Public exposed  variables for tracking humidity and temperature
double humidity = 0;
// Keep track of the average humidity rather than spikes.
double averageHumidity = 0;
double temperature = 0;
int faultCode = 0;
int fanSpeed = 0; // percentage
int fanPulsesPerInterval = 0; // Non volatile int
// How long the fan has been runnning for.
int fanOnCount = 0;

// Public exposed methods
int setFanOnAt(String level);
int setWarnAt(String level);
int fanOn(String command);
int fanOff(String command);
int beep(String command);

// Default, run the fan when humidity > 20%
int fanOnAtHumidityLevel = 20;
int humidityWarningLevel = 30;

// Count of fan pulses
volatile int fanPulseCount = 0;

bool highHumidity = false;
int beepedCount =0;
int publishCount = 0;
bool muted = false;
// Default to false, gets set to true when the fans been running for too long
bool gelNeedsReplacing = false;

// 2 minute timer to run a general refresh 
// for the air circulation.
Timer refreshTimer(120 * 1000, runRefreshCycle);

void setup() {
    
    // First time deployment only
    // Address 0: Hardware version
    if (EEPROM.read(0) == 0xFF) {
        // EEPROM not initialized. store defaults.
        EEPROM.write(0, 4);
        EEPROM.write(1, fanOnAtHumidityLevel);
        EEPROM.write(2, humidityWarningLevel);  
    }
    
    // Take control of the spark LED and make it dimmer as it's very
    // bright by default.
    RGB.control(true);
    RGB.brightness(100);
    
    // Set blue to show initializing.
    RGB.color(0, 0, 255);
    
    pinMode(fanPulseIn, INPUT_PULLUP);
    pinMode(fanControl, OUTPUT);
    pinMode(buzzer, OUTPUT);
    pinMode(highHumidityLed, OUTPUT);
    pinMode(fanLed, OUTPUT);
    pinMode(oshLed, OUTPUT);
    
    digitalWrite(fanControl, LOW);
    digitalWrite(buzzer, LOW);
    digitalWrite(highHumidityLed, HIGH);
    digitalWrite(fanLed, HIGH);
    digitalWrite(oshLed, HIGH);
    
    // Load in the settings
    fanOnAtHumidityLevel = EEPROM.read(1);
    if (fanOnAtHumidityLevel == 0xFF) {
        fanOnAtHumidityLevel = 20;
    }
    
    humidityWarningLevel = EEPROM.read(2);
    if (humidityWarningLevel == 0xFF) {
        humidityWarningLevel = 30;
    }
    
    Particle.publish("Status", "Setting FanOn at: " + String(fanOnAtHumidityLevel) + "%, WarnAt: " + String(humidityWarningLevel) + "%");
    delay(1000);
    
    // Setup I2C as master.
    // Appears bug in Particle where CLOCK_SPEED_400KHZ is undefiend.
    // Works with Core but not Photon.
    // For now default to 100KHZ which appears to work for the DTH21 anyway
    // Wire.setSpeed(CLOCK_SPEED_400KHZ);
    Wire.begin();
    
    // Initialize Temperature and humidity
    if (!humiditySensor.begin()) {
        RGB.color(255, 0, 00);
        Particle.publish("Status", "Humidity Sensor setup failed.");
        // Delay to ensure the message went.
        delay(2000);
    }
    
    // register the cloud function
    Particle.function("setFanOnAt", setFanOnAt);
    Particle.function("setWarnAt", setWarnAt);
    Particle.function("fanOn", fanOn);
    Particle.function("fanOff", fanOff);
    Particle.function("beep", beep);
    Particle.function("mute", mute);
    
    attachInterrupt(fanPulseIn, fanPulse, FALLING);
    
    doBeep(2);
    Particle.publish("Status", "Setup complete. V4.3.4");
    
    refreshTimer.start();
    RGB.color(0, 0, 0);
    delay(250);
}

int loopCounter = 0;
void loop() {
    
    // Indicate blue for reading.
    RGB.color(0, 0, 255);
    
    // Read measurements
    readHumidity();
    readTemperature();
    // Stop fan immediatly on high humidity
    stopFanOnHighHumidity();
    setLedForHumidityLevel();
    
    // Update control/output based on measurements
    if (loopCounter > 10) {
        updateFanControl();
        beepIfNeeded();
        
        // Store the fan pulses during this loop
        // TODO: This is only appropriate if the fan
        // is actually running.
        fanPulsesPerInterval = fanPulseCount;
        fanPulseCount = 0;
        
        updateTinamous();
        // Fault code stays for a publish
        // but clear after that so we can track constant
        // faults
        faultCode = 0;
        loopCounter = 0;
    }
    
    loopCounter++;
    
    // 1s loop cycle to read humidity for rapid feedback with 
    // stopping the fan when in free air.
    delay(1000);
} 

void updateTinamous() {
    // Only publish the 
    if (humidity > humidityWarningLevel && !highHumidity) {
        highHumidity = true;
        Particle.publish("Status", "High Humidity");
    }
    
    if (humidity < fanOnAtHumidityLevel) {
        highHumidity = false;
    }
    
    String senml = "{'n':'Humidity', 'v':" + String(humidity) + "}";
    senml+=",{'n':'averageHumidity', 'v':" + String(averageHumidity) + "}";
    senml+=",{'n':'temperature','v':" + String(temperature) + "}";
    senml+=",{'n':'fanOnCount','v':" + String(fanOnCount) + "}";
    senml+=",{'n':'replaceGel','v':" + String(gelNeedsReplacing) + "}";
    senml+=",{'n':'faultCode','v':" + String(faultCode) + "}";
    
    // If the fan is running include the fan pulses.
    // If == 0 then it's likely the fan was on
    // during the last time slice.
    if (fanSpeed >= 0) {
        senml+=",{'n':'fanPulses','v':" + String(fanPulsesPerInterval) + "}";
        senml+=",{'n':'fanTimeRemain','v':" + String(fanSpeed * 10) + "}";
    }
    
    Particle.publish("senml", "{'e': [" + senml + "]}");
}

void setLedForHumidityLevel() {

    digitalWrite(highHumidityLed, LOW);
    digitalWrite(oshLed, LOW);
    
    // If > 30, Red - very high humidity
    // if > 20, higher than wanted
    // Otherwise Green - all is well...
    if (humidity > humidityWarningLevel) {
        RGB.color(255, 0, 0);
        RGB.brightness(100);
        digitalWrite(highHumidityLed, HIGH);
    } else if (humidity > fanOnAtHumidityLevel) {
        RGB.color(255, 255, 0);
        RGB.brightness(100);
        digitalWrite(highHumidityLed, HIGH);
    } else {
        RGB.color(0, 255, 0);
        RGB.brightness(50);
        digitalWrite(oshLed, HIGH);
    }
}

void readHumidity() {
    float h = humiditySensor.readHumidity();
    if (h < 100) {
        humidity = (double)h;
        // TODO: Compute average humidity.
        averageHumidity = humidity;
    } else {
        faultCode = h;
    }
}

void readTemperature() {
    float t = humiditySensor.readTemperature();
    if (t<100) {
        temperature = (double)t;
    } else {
        faultCode = t;
    }
}

// More frequent check for humidity and fan control
void stopFanOnHighHumidity() {
    if (humidity > humidityWarningLevel ) {
        setFan(0);
    }
}

void updateFanControl() {

    int newFanOnCount = fanSpeed - 1;
   
    if (humidity > humidityWarningLevel ) {
        // If measured humidity is above 30% (humidityWarningLevel) then
        // don't run the fan as the unit is most likely in free air
        // and this will ruin the silica gel quickly.
        newFanOnCount = 0;
        
        // The humidity will gradually drop once the unit is in a controlled environment
        // and then the fan can resume 
        // Particle.publish("Status", "Humidity (" + String(humidity) + ")> " + String(humidityWarningLevel) + "%. Stopping fan to prevent saturating gel");
    } else if (humidity < 5) {
        // Humidity is low, don't bother to run the fan
        // so to avoid over using the silica gel tying to create a very low
        // humidity environment.
        newFanOnCount = 0;
    } else if (humidity >  fanOnAtHumidityLevel) {
        // Humidity is above fanOnAtHumidityLevel and below humidityWarningLevel
        // so run the fan to try and drop the humidity.
        
        // If gel needs replacing don't run the fan now
        // otherwise it will run constantly.
        
        // System will be power cycled when gel is replaced so the flag resets.
        // Manually switching the fan on will also reset this.
        if (!gelNeedsReplacing && newFanOnCount == 0) {
            // If the fan is off and the gel doesn't need replacing then
            // switch the fan on.
            newFanOnCount = 1;
        }
        
        // If the fans been on for n counts and the humidity has not dropped below our
        // fanOnAt level then flag the gel as shot.
        if (fanOnCount > 12) { 
            Particle.publish("status", "Gel is shot, fan's been running for too long. Disabling fan....");
            gelNeedsReplacing = true;
        }
    } else {
        // Humidity is below the level we need to wory about
        // so don't run the fan, use only the timed interval.
        // let the fan run for the timer set.
    }
    
    if (newFanOnCount < 0) {
        newFanOnCount = 0;
    }
    
    setFan(newFanOnCount);
}

void beepIfNeeded() {
    // Beep a max of n-times when the humidity goes out of range
    // Once it's back in range reset the counter.
    if (humidity > humidityWarningLevel) {
        if (beepedCount<5) {
            doBeep(5);
            beepedCount++;
        } else {
            /// Silent beep beep beep to stop it being annoying
        }
    } else if (humidity < (humidityWarningLevel - 5)) {
        //Particle.publish("Status", "Resetting beep counter as humidity < warning level");
        beepedCount = 0;
    }
}

// =====================================================
// Particle Method
int setFanOnAt(String level) {
    // This relies on level being a single numeric value.
    fanOnAtHumidityLevel = level.toInt();
    EEPROM.write(1, fanOnAtHumidityLevel);
    Particle.publish("Status", "Setting fan trigger humidity level at: " + String(fanOnAtHumidityLevel) + "%");
    return fanOnAtHumidityLevel;
}

// Particle Method
int setWarnAt(String level) {
    humidityWarningLevel = level.toInt();
    EEPROM.write(2, humidityWarningLevel);
    Particle.publish("Status", "Setting humidity waring level at: " + String(humidityWarningLevel) + "%");
    return humidityWarningLevel;
}

// Particle Method
int fanOn(String command)
{
    Particle.publish("Status", "Switching fan on for max. 10 minutes (Reset gelNeedsReplacing).");
    // On for 10 mins (6 iterations per min)
    setFan(10 * 6);
    return 10*6;
}

// Particle Method
int fanOff(String command)
{
    Particle.publish("Status", "Switching fan off.");
    
    // On for 0 iterations
    setFan(0);
    return 0;
}

// Particle Method
int mute(String command) {
    int toMute = command.toInt();
    if (toMute == 1) {
        muted = true;
        Particle.publish("Status", "Muted.");
    } else {
        muted = false;
        Particle.publish("Status", "Un muted.");
    }
    
    return muted ? 1 : 0;
}

int beep(String command)
{
    doBeep(command.toInt());
    return 0;
}

// ==========================================================
// Helpers
//
// Use the number of cycles that the fan should be on for.
void setFan(int fanCount) {
    
    int newfanSpeed = fanCount; //max(speed, fanOverrideSpeed);
    
    // Store in the Particle variable
    fanSpeed = newfanSpeed;
    
    // TODO: Use AnalogWrite when fan connected to it.
    if (fanSpeed > 0) {
        digitalWrite(fanControl, HIGH);
        digitalWrite(fanLed, HIGH);
        fanOnCount++;
    } else {
        digitalWrite(fanControl, LOW);
        digitalWrite(fanLed, LOW);
        fanOnCount = 0;
    }
}

// Beep to indicate a problem (and flash the red LED)
void doBeep(int count) {
   
    for(int i=0; i<count; i++) {
        digitalWrite(highHumidityLed, LOW);
        
        if (!muted) {
            for(int j=0; j<150; j++) {
                digitalWrite(buzzer, HIGH);
                delay(1);
                digitalWrite(buzzer, LOW);
                delay(1);
            }
        }
        
        delay(100);
        digitalWrite(highHumidityLed, HIGH);
        delay(100);
    }
    Particle.publish("Status", "Beep, Beep, Beep... (Beep Count: " + String(beepedCount) + ")");
}

// =================================================
// ISR and timers

void runRefreshCycle() {
    // Run the fan for 20 seconds every time the refresh cycle is requested.
    //Particle.publish("Status", "Running fan for general air refresh");
    setFan(3);
}

void fanPulse() {
    fanPulseCount++;
}

Github Repository

Credits

Stephen Harrison

Stephen Harrison

18 projects • 51 followers
Founder of Tinamous.com, software developer, hardware tinkerer, dyslexic. @TinamousSteve

Comments