Overview
Accidentally leaving a door open for a long time will ruin the contents of a freezer if nobody knows about it. This alarm will notify folks when the door has been left open for more than 10 minutes, and it will start beeping after 5 minutes. A motion sensor will restart the door timers when activated, as someone could spend >5 minutes loading the freezer. This alarm is designed to fit a double door commercial freezer.
There are 3 phases of the alarm
- Door(s) open <5 minutes - no action.
- Door(s) open >5 minutes with no motion - pre-alarm beeping.
- Door(s) open >10 minutes with no motion - beep more and email a notification.
The purpose of the pre-alarm beeping is to draw attention to the freezer so someone can close it before the alarm goes off.
Page last edited March 08, 2024
Text editor powered by tinymce.
CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
Below are links to CircuitPython for the Feather ESP32-S3 8MB No PSRAM and the Feather ESP32-S3 4MB Flash 2MB PSRAM. Be sure to choose the one that matches your board.
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if needed.
Double-click the reset button (highlighted in red above), and you will see the RGB status LED(s) turn green (highlighted in green above). If you see red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or hub.
For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset again. The second tap needs to happen while the LED is still purple.
If you do not see the LED turning purple, you will need to reinstall the UF2 bootloader. See the Factory Reset page in this guide for details.
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called FTHRS3BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to FTHRS3BOOT.
Page last edited March 08, 2024
Text editor powered by tinymce.
Hardware Assembly
Step 2: Take the soldered protoboard and put the female headers onto the pins. Then add and solder the header on to the Feather
Step 3: Attach pins to the holes outlined, use two groups of 3 pins and a single pin
D9 - Yellow: Left door
D5 - Green: Motion sensor
D6 - Orange: Right door
3 Ground pins for door sensors and motion sensor
1 power pin for the motion sensor.
This is what it looks like from the top of the board, you will be soldering the pins in with the board upside down.
Use the breadboard to help solder in the pins. Add an extra strip of pins to keep the board level, make sure not to solder the extra pins. This diagram is reversed to line up with the upside down board
Step 4: Polarity matters make sure the positive side is going to the io pins.
Pins for the buzzer positive pin (+)
D13 and D11 in blue
Pins for the buzzer negative pin (-)
2 of the remaining ground pins in purple
The buzzers take up 4 pin spaces, when you have the wires in place, add the buzzers then solder the wires and buzzers together.
Step 5: Wire up the door sensors and the motion sensor according to this diagram
D9 - Yellow: Left door
D5 - Green: Motion sensor
D6 - Orange: Right door
Measure the distance the board will be from where the contacts will be on the freezer. Give yourself a bit of extra length for slack or adjustment. Cut wires and solder.
Page last edited March 08, 2024
Text editor powered by tinymce.
3D Printed Case
You can use any 3D printed case you want. I chose a minimal design from Thingiverse. The purpose of a case is to prevent the bottom of the Feather from coming in contact with the metal surface on top of the freezer.
Page last edited March 08, 2024
Text editor powered by tinymce.
Setting up settings.toml
settings.toml is a separate text file from your CircuitPython code.py to contain credentials, such as WiFi name & password and Adafruit IO credentials.
Open a text editor and put copy the text below and paste it in. Change the fields to correspond to your WiFi and Adafruit IO account. Save as settings.toml.
# This file is where you keep private settings, passwords, and tokens! # If you put them in the code you risk committing that info or sharing it CIRCUITPY_WIFI_SSID="your-wifi-ssid" CIRCUITPY_WIFI_PASSWORD="your-wifi-password" ADAFRUIT_AIO_USERNAME="my_username" ADAFRUIT_AIO_KEY="my_key" timezone="America/New_York" # http://worldtimeapi.org/timezones
Page last edited March 08, 2024
Text editor powered by tinymce.
Code Breakdown
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, plus the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2022 Matt Desmarais for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from os import getenv
import time
import ssl
import microcontroller
import socketpool
import wifi
import board
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_io.adafruit_io import IO_MQTT
import digitalio
from adafruit_debouncer import Debouncer
#setup buzzer1
buzzer1 = digitalio.DigitalInOut(board.D13)
buzzer1.direction = digitalio.Direction.OUTPUT
#setup buzzer2
buzzer2 = digitalio.DigitalInOut(board.D11)
buzzer2.direction = digitalio.Direction.OUTPUT
#setup left door switch
leftdoor = digitalio.DigitalInOut(board.D5)
leftdoor.direction = digitalio.Direction.INPUT
leftdoor.pull = digitalio.Pull.UP
leftswitch = Debouncer(leftdoor)
#setup right door switch
rightdoor = digitalio.DigitalInOut(board.D9)
rightdoor.direction = digitalio.Direction.INPUT
rightdoor.pull = digitalio.Pull.UP
rightswitch = Debouncer(rightdoor)
#setup motion sensor
pir = digitalio.DigitalInOut(board.D6)
pir.direction = digitalio.Direction.INPUT
motion = Debouncer(pir)
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
ssid = getenv("CIRCUITPY_WIFI_SSID")
password = getenv("CIRCUITPY_WIFI_PASSWORD")
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
aio_key = getenv("ADAFRUIT_AIO_KEY")
if None in [ssid, password, aio_username, aio_key]:
raise RuntimeError(
"WiFi and Adafruit IO settings are kept in settings.toml, "
"please add them there. The settings file must contain "
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
)
# WiFi
try:
print(f"Connecting to {ssid}")
wifi.radio.connect(ssid, password)
print(f"Connected to {ssid}!")
# Wi-Fi connectivity fails with error messages, not specific errors, so this except is broad.
except Exception as e: # pylint: disable=broad-except
print("Failed to connect to WiFi. Error:", e, "\nBoard will restart in 5 seconds.")
time.sleep(5)
microcontroller.reset()
# Create a socket pool
pool = socketpool.SocketPool(wifi.radio)
# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(
broker="io.adafruit.com",
username=aio_username,
password=aio_key,
socket_pool=pool,
ssl_context=ssl.create_default_context(),
)
# Define callback functions which will be called when certain events happen.
def connected():
print("Connected to Adafruit IO! Listening for Freezer changes...")
# Initialize Adafruit IO MQTT "helper"
io = IO_MQTT(mqtt_client)
# Set up the callback methods above
io.on_connect = connected
#start time for timed uploads
start = int(time.time()/300)
#door timers set start times to now
start1 = time.monotonic()
start2 = time.monotonic()
#door alarms set to False
prealarm = False
alarm1 = False
alarm2 = False
door1feed = "unit-6.door1"
door2feed = "unit-6.door2"
motionfeed = "unit-6.motion"
alarmfeed = "unit-6.alarm"
resolvedfeed = "unit-6.resolved"
#reconnectedfeed = "unit-6.reconnected"
#initial publishes all zeros
try:
io.connect()
# Adafruit IO fails with internal error types and WiFi fails with specific messages.
# This except is broad to handle any possible failure.
except Exception as e: # pylint: disable=broad-except
print("Failed to get or send data, or connect. Error:", e,
"\nBoard will restart in 20 seconds.")
time.sleep(20)
microcontroller.reset()
io.publish(alarmfeed, 0)
io.publish(resolvedfeed, 0)
while True:
try:
# If Adafruit IO is not connected...
if not io.is_connected:
# Connect the client to the MQTT broker.
print("Connecting to Adafruit IO...")
io.connect()
time.sleep(1)
#update leftswitch
leftswitch.update()
#if door closed upload to IO
if leftswitch.fell:
print('left closed')
io.publish(door1feed, 1)
time.sleep(.25)
io.publish(door1feed, 0)
#if door opened upload to IO, set start1 to now
if leftswitch.rose:
print('left opened')
io.publish(door1feed, 1)
start1 = time.monotonic()
#if door remains open
if leftswitch.value:
print('still left open')
#if door remains closed, reset start1
else:
#door still closed reset timer
print('still left closed')
start1 = time.monotonic()
#update rightswitch
rightswitch.update()
#if door closed upload to IO
if rightswitch.fell:
print('right closed')
io.publish(door2feed, 1)
time.sleep(.25)
io.publish(door2feed, 0)
#if door opened upload to IO, set start2 to now
if rightswitch.rose:
print('right opened')
io.publish(door2feed, 1)
start2 = time.monotonic()
if rightswitch.value:
print('still right open')
#door still closed reset timer
else:
print('still right closed')
start2 = time.monotonic()
#if a door closes update both switches
if rightswitch.fell or leftswitch.fell:
rightswitch.update()
leftswitch.update()
#if both doors are closed
if not rightswitch.value and not leftswitch.value:
print('doors just closed')
#if prelarm is true then set it to False
if prealarm is True:
buzzer1.value = False
#if an alarm is true then upload to IO alarm resolved
if alarm1 or alarm2:
#publish 0 to alarm feed
io.publish(alarmfeed, 0)
#buzzers off/Alarms to False
buzzer1.value = False
buzzer2.value = False
alarm1 = False
alarm2 = False
#toggle alarm resolved feed to send email notification
io.publish(resolvedfeed, 1)
time.sleep(5)
io.publish(resolvedfeed, 0)
#check motion sensor if there is no alarm
if(not alarm1 and not alarm2 and not prealarm):
#update pir sensor
motion.update()
#if motion stopped
if motion.fell:
print('motion stopped')
#publish 0 to motion feed
io.publish(motionfeed, 1)
time.sleep(.25)
io.publish(motionfeed, 0)
#if motion started
if motion.rose:
print('motion detected')
#reset start times
start1 = time.monotonic()
start2 = time.monotonic()
#if continued motion
elif motion.value:
print('still motion')
io.publish(motionfeed, 1)
time.sleep(5)
#if continued no motion
else:
print('no motion')
print("\n")
# Explicitly pump the message loop to avoid MQTT timeouts.
io.loop()
#check difference between time now and start times if more than N seconds start beeping
if (((time.monotonic() - start1) >= 300) or ((time.monotonic() - start2) >= 300)):
prealarm = True
#beeping
buzzer1.value = True
time.sleep(.5)
buzzer1.value = False
#check if difference between time now and start1 if more than X seconds turn on buzzer2
if (alarm1 is False and ((time.monotonic() - start1) >= 600)):
alarm1 = True
buzzer2.value = True
#publish 1 to alarm feed
io.publish(alarmfeed, 1)
#check if difference between time now and start2 if more than X seconds turn on buzzer2
if (alarm2 is False and ((time.monotonic() - start2) >= 600)):
alarm2 = True
buzzer2.value = True
#publish 1 to alarm feed
io.publish(alarmfeed, 1)
#check if 300 seconds have passed compared to start time, if so publish values
if int(time.time()/300) > start:
print("PUBLISH EVERY FIVE MINUTES")
start = int(time.time()/300)
io.publish(door1feed, int(leftswitch.value))
io.publish(door2feed, int(rightswitch.value))
io.publish(motionfeed, int(motion.value))
# Adafruit IO fails with internal error types and WiFi fails with specific messages.
# This except is broad to handle any possible failure.
except Exception as e: # pylint: disable=broad-except
print("Failed to get or send data, or connect. Error:", e,
"\nBoard will restart in 20 seconds.")
time.sleep(20)
microcontroller.reset()
How It Works
Opening a door starts a timer, if the timer hits 5 minutes while there is no motion detected then the first buzzer starts doing prealarm beeping to catch someones attention to close it before 10 minutes elapsed of the door open with no motion detected the 2nd buzzer kicks in and an email is sent.
Libraries
- time
- ssl
- microcontroller
- socketpool
- wifi
- board
- adafruit_minimqtt
- adafruit_io.adafruit_io
- digitalio
- adafruit_debouncer
import time import ssl import microcontroller import socketpool import wifi import board import adafruit_minimqtt.adafruit_minimqtt as MQTT from adafruit_io.adafruit_io import IO_MQTT import digitalio from adafruit_debouncer import Debouncer
#setup buzzer1 buzzer1 = digitalio.DigitalInOut(board.D13) buzzer1.direction = digitalio.Direction.OUTPUT #setup buzzer2 buzzer2 = digitalio.DigitalInOut(board.D11) buzzer2.direction = digitalio.Direction.OUTPUT
Door Sensors
Setup Debouncer for both door switches pins D9 & D5. If once you install your alarm you realize that the doors are reversed, swap the pins for leftdoor and rightdoor.
#setup left door switch leftdoor = digitalio.DigitalInOut(board.D9) leftdoor.direction = digitalio.Direction.INPUT leftdoor.pull = digitalio.Pull.UP leftswitch = Debouncer(leftdoor) #setup right door switch rightdoor = digitalio.DigitalInOut(board.D5) rightdoor.direction = digitalio.Direction.INPUT rightdoor.pull = digitalio.Pull.UP rightswitch = Debouncer(rightdoor)
#setup motion sensor pir = digitalio.DigitalInOut(board.D6) pir.direction = digitalio.Direction.INPUT motion = Debouncer(pir)
Variables
- start 1 & 2: is the "start" time of the timer
- alarm 1 & 2: is based the state of the timer
- feed names: door1, door2, motion, alarm and resolved
#door timers set start times to now start1 = time.monotonic() start2 = time.monotonic() #door alarms set to False alarm1 = False alarm2 = False door1feed = "unit-6.door1" door2feed = "unit-6.door2" motionfeed = "unit-6.motion" alarmfeed = "unit-6.alarm" resolvedfeed = "unit-6.resolved"
Door Logic
This section determines the state of the door, everything is repeated for the right door. If the door was closed (fell) then publish 1 then 0 to the door feed. If the door was opened (rose) publish 1 to door feed and reset start time. If the door is open do nothing and if the door is closed reset the start time.
#update leftswitch
leftswitch.update()
#if door closed upload to IO
if leftswitch.fell:
print('left closed')
io.publish(door1feed, 1)
time.sleep(.25)
io.publish(door1feed, 0)
#if door opened upload to IO, set start1 to now
if leftswitch.rose:
print('left opened')
io.publish(door1feed, 1)
start1 = time.monotonic()
#if door remains open
if leftswitch.value:
print('still left open')
#if door remains closed, reset start1
else:
#door still closed reset timer
print('still left closed')
start1 = time.monotonic()
Alarm resolution
This section checks if a door just closed, then if both doors are closed, then if an alarm is true, if it is all true then resolved feed is toggled to send the alarm resolved notification
#if a door closes
if rightswitch.fell or leftswitch.fell:
#if both doors are closed
if not rightswitch.value and not leftswitch.value:
print('doors just closed')
#if an alarm is true then upload to IO alarm resolved
if alarm1 or alarm2:
#publish 0 to alarm feed
io.publish(alarmfeed, 0)
#buzzers off/Alarms to False
buzzer1.value = False
buzzer2.value = False
alarm1 = False
alarm2 = False
#toggle alarm resolved feed to send email notification
io.publish(resolvedfeed, 1)
time.sleep(5)
io.publish(resolvedfeed, 0)
Motion Sensor Logic
This section will only update the motion sensor if the alarms are not True, then it will update the motion sensor, if motion stopped publish 1 then 0 to motionfeed to make the graphs look nice. If motion was detected publish 1 to motionfeed and reset both start times.
#check motion sensor if there is no alarm
if(alarm1 == False and alarm2 == False):
#update pir sensor
motion.update()
#if motion stopped
if motion.fell:
print('motion stopped')
#publish 0 to motion feed
io.publish(motionfeed, 1)
time.sleep(.25)
io.publish(motionfeed, 0)
#if motion started
if motion.rose:
print('motion detected')
#publish 1 to motion feed
io.publish(motionfeed, 1)
#reset start times
start1 = time.monotonic()
start2 = time.monotonic()
#if continued motion
if motion.value:
print('still motion')
#if continued no motion
else:
print('no motion')
Prealarm & Alarm Logic
If either door has been open for more than 5 minutes start beeping. then if either door has been open for more than 10 minutes activate the second buzzer and publish 1 to the alarm feed.
#check difference between time now and start times if more than N seconds start beeping
if (((time.monotonic() - start1) >= 300) or ((time.monotonic() - start2) >= 300)):
#beeping
buzzer1.value = True
time.sleep(.5)
buzzer1.value = False
#check if difference between time now and start1 if more than X seconds turn on buzzer2, publish 1 to alarm feed
if (alarm1 == False and ((time.monotonic() - start1) >= 600)):
alarm1 = True
buzzer2.value = True
#publish 1 to alarm feed
io.publish(alarmfeed, 1)
#check if difference between time now and start2 if more than X seconds turn on buzzer2, publish 1 to alarm feed
if (alarm2 == False and ((time.monotonic() - start2) >= 600)):
alarm2 = True
buzzer2.value = True
#publish 1 to alarm feed
io.publish(alarmfeed, 1)
if(int(time.time()/300) > start):
print("PUBLISH EVERY FIVE MINUTES")
start = int(time.time()/300)
io.publish(door1feed, int(leftswitch.value))
io.publish(door2feed, int(rightswitch.value))
io.publish(motionfeed, int(motion.value))
Page last edited March 08, 2024
Text editor powered by tinymce.
Adafruit IO Setup
Adafruit IO is a service to send data to and be able to perform actions or visualize data. The basic tier is free. You can learn more in the guide below.
Freezer Alarm Project Setup
Open Adafruit IO and log in to your account. Start a new project and follow the steps below:
You will need top create 5 feed keys for the code:
- Alarm
- Door 1
- Door 2
- Motion
- Alarm Resolved
You also will be creating two IO Actions:
-
Alarm - send alarm notification
-
Alarm Resolved - send resolved notification
Setup the Dashboard
Create a new Dashboard
- From Dashboard Settings select Create New Block, add a line chart for the last 2 hours for the motion feed, check Stepped Line.
- From Dashboard Settings select Create New Block, add a line chart for the last 2 hours for the door feeds, check Stepped Line.
- From Dashboard Settings select Create New Block, add an indicator for motion, make sure to set the Conditions as < 1. Use the Test Value to make sure 0 is green and 1 is red.
- Repeat step 4 for the both doors and alarm feeds.
If you make more than one alarm double up on the indicators and combine the line charts
- From Dashboard Settings select Create New Block, add a line chart for the last 2 hours for the motion feeds, check Stepped Line
- From Dashboard Settings select Create New Block, add a line chart for the last 2 hours for all of the door feeds, check Stepped Line
- From Dashboard Settings select Create New Block, add indicator for a motion sensor, make sure to set the Conditions as < 1.
- Repeat step 3 for all motion sensors, doors, and alarms
Page last edited March 08, 2024
Text editor powered by tinymce.
Installation
Place the Feather board in it's case on top of the freezer and run the door sensors and motion sensor through the grate.
Hold the door sensors in place and trace the inside corder with a permanent marker, repeat for the 2nd door.
Attach the 15ft power cable to the alarm. Leave the extra lengths coiled up on the top of the freezer and then plug it in to a USB wall charger (5v 2a). The alarm will beep when power is applied.
Page last edited March 08, 2024
Text editor powered by tinymce.