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

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)

try:
    from secrets import secrets
except ImportError:
    print("WiFi and Adafruit IO credentials are kept in secrets.py - please add them there!")
    raise

# Add your Adafruit IO Username and Key to secrets.py
# (visit io.adafruit.com if you need to create an account,
# or if you need to obtain your Adafruit IO key.)
aio_username = secrets["aio_username"]
aio_key = secrets["aio_key"]

# WiFi
try:
    print("Connecting to %s" % secrets["ssid"])
    wifi.radio.connect(secrets["ssid"], secrets["password"])
    print("Connected to %s!" % secrets["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=secrets["aio_username"],
    password=secrets["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

Buzzers

Initialize the buzzers pin D13 & D11

#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)

Motion Sensor

Setup Debouncer for the motion sensor pin D6

#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)

Updating every 5 minutes

Upload data every 5 minutes to keep graphs current

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))

This guide was first published on Oct 12, 2022. It was last updated on Mar 29, 2024.

This page (Code Breakdown) was last updated on Mar 28, 2024.

Text editor powered by tinymce.