Also, if anyone is interested in the Python script:
import requests
import pyaudio
import pydub
import numpy as np
from numpy import log
from scipy.fft import fft
from time import sleep
import threading
Volume Sensitivity, 0.05: Extremely Sensitive, may give false alarms
0.1: Probably Ideal volume
1: Poorly sensitive, will only go off for relatively loud
SENSITIVITY = 1
Alarm frequency (Hz) to detect
TONE = 3100
Bandwidth for detection
BANDWIDTH = 30
How many 46ms blips before we declare a beep?
beeplength = 8
How many beeps before we declare an alarm?
alarmlength = 2
How many false 46ms blips before we declare there are no more beeps?
resetlength = 10
How many reset counts until we clear an active alarm?
clearlength = 1
Enable blip, beep, and reset debug output
debug = False
Show the most intense frequency detected
frequencyoutput = False
NUM_SAMPLES = 2048
SAMPLING_RATE = 44100
GLITCH_PROJECT_URL = ‘’
Whether to process audio files or live audio
PROCESS_AUDIO_FILES = False # Set to False to process live audio instead
Whether to prompt the user for the microphone device index
PROMPT_USER_FOR_DEVICE = False
if PROMPT_USER_FOR_DEVICE:
def list_microphones():
p = pyaudio.PyAudio()
info = p.get_host_api_info_by_index(0)
numdevices = info.get(‘deviceCount’)
for i in range(0, numdevices):
if p.get_device_info_by_host_api_device_index(0, i).get(‘maxInputChannels’) > 0:
print(f"Input Device id {i} - {p.get_device_info_by_host_api_device_index(0, i).get(‘name’)}")
p.terminate()
list_microphones() # This will list all microphones
device_index = int(input("Enter the device index of the microphone you want to use: "))
else:
device_index = 1 # default device index
blipcount = 0
beepcount = 0
resetcount = 0
clearcount = 0
alarm = False
def keep_awake():
while True:
try:
response = requests.get(‘https://seasoned-nasal-slug.glitch.me’)
if response.status_code == 200:
print(f’Successfully pinged your Glitch project at {GLITCH_PROJECT_URL}‘)
else:
print(f’Failed to ping your Glitch project. HTTP Status Code: {response.status_code}’)
except requests.RequestException as e:
print(f’An error occurred: {e}')
for i in range(240): # prints a message every second for 4 minutes
print(f'Running... {i + 1}')
sleep(1)
def process_audio(audio_data):
global blipcount, beepcount, resetcount, clearcount, alarm
normalized_data = audio_data / 32768.0
intensity = abs(fft(normalized_data))[:len(normalized_data) // 2]
frequencies = np.linspace(0.0, float(SAMPLING_RATE) / 2, num=len(normalized_data) // 2)
if frequencyoutput:
which = intensity[1:].argmax() + 1
# use quadratic interpolation around the max
if which != len(intensity) - 1:
y0, y1, y2 = log(intensity[which - 1:which + 2])
x1 = (y2 - y0) * .5 / (2 * y1 - y2 - y0)
# find the frequency and output it
thefreq = (which + x1) * SAMPLING_RATE / NUM_SAMPLES
else:
thefreq = which * SAMPLING_RATE / NUM_SAMPLES
print(f"\t\t\t\tfreq={thefreq}")
if max(intensity[(frequencies < TONE + BANDWIDTH) & (frequencies > TONE - BANDWIDTH)]) > max(
intensity[(frequencies < TONE - 1000) & (frequencies > TONE - 2000)]) + SENSITIVITY:
blipcount += 1
resetcount = 0
if debug:
print("\t\tBlip", blipcount)
if blipcount >= beeplength:
blipcount = 0
resetcount = 0
beepcount += 1
if debug:
print("\tBeep", beepcount)
if beepcount >= alarmlength:
clearcount = 0
alarm = True
print("Alarm!")
beepcount = 0
else:
blipcount = 0
resetcount += 1
if debug:
print("\t\t\treset", resetcount)
if resetcount >= resetlength:
resetcount = 0
beepcount = 0
if alarm:
clearcount += 1
if debug:
print("\t\tclear", clearcount)
if clearcount >= clearlength:
clearcount = 0
print("Cleared alarm!")
alarm = False
response = requests.post('https://seasoned-nasal-slug.glitch.me/alarm', json={'status': 'Alarm!'})
if response.status_code == 200:
print('Successfully sent alarm alert to SmartApp')
else:
print(f'Failed to send alarm alert to SmartApp: {response.status_code}')
def process_live_audio():
pa = pyaudio.PyAudio()
_stream = pa.open(format=pyaudio.paInt16,
channels=1,
rate=SAMPLING_RATE,
input=True,
frames_per_buffer=NUM_SAMPLES,
input_device_index=device_index)
print("Alarm detector working. Press CTRL-C to quit.")
try:
while True:
try:
while _stream.get_read_available() < NUM_SAMPLES:
sleep(0.01)
audio_data = np.frombuffer(_stream.read(_stream.get_read_available()), dtype=np.int16)
process_audio(audio_data)
except KeyboardInterrupt:
# Handle KeyboardInterrupt immediately when it occurs
raise
except KeyboardInterrupt:
print("\nTerminating...")
_stream.stop_stream()
_stream.close()
pa.terminate()
def process_audio_files():
for filename in [‘smoke1.mp3’, ‘smoke2.mp3’]:
print(f"Processing {filename}“) # Debugging output
audio = pydub.AudioSegment.from_mp3(filename)
audio = audio.set_frame_rate(SAMPLING_RATE) # Ensure correct sampling rate
audio = audio.set_channels(1) # Convert to mono if necessary
samples = np.array(audio.get_array_of_samples())
print(f"Number of samples: {len(samples)}”) # Debugging output
process_audio(samples)
if name == “main”:
# Start the keep_awake function in a separate thread
threading.Thread(target=keep_awake, daemon=True).start()
if PROCESS_AUDIO_FILES:
process_audio_files()
else:
process_live_audio()