There are many things that should be stored at a more or less specific relative humidity. If not, they will dry out or get moldy. There are solutions for this, such as Bodeva. These packets absorb or release moisture to maintain equilibrium at a specific relative humidity. The problem is that these things wear out over time and stop working. But how do you know? You can put indicator paper in the container, but unless it’s clear you have to open it to check, and that messes up the equilibrium.

The answer? Technology, naturally.


When this need arose, I was playing around with the new SAMD21 boards from Adafruit, specifically using CircuitPython. The Trinket M0 was one such board. Tiny and just capable enough to run some simple python code. It exposes an I2C interface so connecting sensors is easy. And I just needed one: a relative humidity sensor and I knew just the sensor for the job.

For my smarthome sensor nodes, I am using the Si7021 temperature and relative humidity sensor. Adafruit has this on a breakout board which makes it easy to use (otherwise it is a fairly tiny SMT package that is not very hand soldering friendly. The breakout also has the benefit of being breadboardable while you are prototyping.


Additionally I needed some way of alerting when humidity is out of range. For this I originally used a digitally controlled buzzer (i.e. one output line to turn it on/off). For this I found this little guy. Having a single output to control it keeps the circuitry and code simple and small. More to the point, the TrinketM0 didn’t have enough flash for its CircuitPython build to include the PWM library (pulseio). I also added an external neopixel that I use to indicate the problem: whether the humidity is too high or too low. Interesting, but secondary to knowing that it’s not what it should be.

Since making the first deployed prototype, the CircuitPython build switched to a smaller math library, freeing up enough space to WOOT! add in the pulseio library. So I switched my design to use a plain piezo buzzer that my code can control the pitch of. Since I can now use sound to indicate the problem, I decided to remove the neopixel. This will let me simplify the eventual 3D printed case since I won’t need to expose a light.

Humidity change in a closed environment should be slow, and those humidity control packs last quite a while, so I felt no need to check frequently. Taking a humidity reading every couple hours is plenty. This also means less power being used over a period of time, so battery power is easier to deal with. I decided to use a 500 mAh lipo battery as it was about the same size as the space I was building for and would tuck under the circuit board.

To make this work I used the TPL5110 breakout that will power up the Trinket every so often (up to about every 2 hours). When the job is done a single output tells the 5110 to power down the Trinket and start the timer again. This make the code much simpler: no loop, no sleeping, just check the humidity and shut down if it’s good. If not, alert until it is, then shut down.



The main body of code reflects this directly:

pixels = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
pixels[0] = (0, 0, 0)
sleep_pin.value = True

The first two lines aren’t even part of the logic, they simply turn off the Trinket’s onboard dotstar to further save power.  Then take the reading and complain while it’s bad, then turn off.

Let’s look at warn_if_out_of_range:

def warn_if_out_of_range(comparison):
    while comparison != 0:
        chirp(comparison > 0)
        time.sleep(alert_interval - alert_duration)
        comparison = check_rh()

The next function to look at is check_rh which read the humidity and returns whether it’s below, above, or in the target range:

def check_rh(): 
    relative_humidity = si7021.relative_humidity
    if relative_humidity < (target_humidity - 5):
        return -1
    elif relative_humidity > (target_humidity + 5):
        return 1
    else: return 0

The chirp function plays a falling or rising tone as requested based on whether the humidity is below or above the target range:

def chirp(direction):
    freqs = rising_tones if direction else falling_tones
    pwm = pulseio.PWMOut(board.D3, duty_cycle = 0x7fff, frequency=freqs[0], variable_frequency=True)
    for f in freqs:
        pwm.frequency = f

See the repo for falling_tones and rising_tones. They are simply arrays of frequencies for each tone sequence.

Beyond that, there the imports, constants, and setting up the pins and interfaces.  See the repo at


Once the pieces were selected, the circuit breadboarded, and the software written, it was time to make it real.  This started with a circuit board.



Since the idea was to embed this hardware in a jar lid, and there would be a lipo battery tucked under the board, I wanted to make the build as thin as possible. Standard header strips does not help with that goal. So taking some inspiration from the ESP8266 module and a project by Scott Shawcroft I did some trimming.


IMG_1121Now I could mount the Trinket & TPL5110 directly on the board without needing headers.


To do this I first put a blob of solder on one hole on the board, then tacked the board down using that, making sure things were aligned correctly. Then it was just a matter of finishing the job.


To make sure the connection were complete I touched up the underside of the board.

IMG_1124Once the boards were in place, I soldered the mounting holes for additional physical stability.

IMG_1125Mounting the buzzer and battery connector was straight forward. The hacking header holes I used for the buzzer was a bit off and will be improved on version 2.

IMG_1127The final piece of the main board is the sensor connector.

IMG_1128The sensor is mounting using a matching bit of female header to mount under the main board.

IMG_1129IMG_1130Now to mount this assembly onto the jar lid.  After locating where I wanted the header to go, I cut the centerline and ends. Once cut, the female header will be pushed through from the inside which will hold it in place and provide enough of a seal.



The main board then mounts via the headers.


The battery tucks in between the lid and board. Note that I shortened the battery wires.


And finally, it’s complete.


All that’s left is to 3D print a cover for the whole thing.