Skip to content

Commit

Permalink
Implemented Alarms & Reminders ⏰ (#33)
Browse files Browse the repository at this point in the history
- Added alarm sound to contrib
- Uses `threading.Timer` for scheduling alarms & reminders
  • Loading branch information
judahpaul16 authored May 19, 2024
1 parent f2183eb commit ffa7d6b
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 20 deletions.
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RUN dpkg --add-architecture armhf
# Install necessary packages
RUN /bin/bash -c "yes | add-apt-repository universe && \
dpkg --add-architecture armhf && apt-get update && \
apt-get install -y --no-install-recommends supervisor \
apt-get install -y --no-install-recommends supervisor nano neovim \
avahi-daemon avahi-utils libnss-mdns dbus iputils-ping \
build-essential curl git libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
libsqlite3-dev llvm libncursesw5-dev xz-utils tk-dev libraspberrypi-bin \
Expand Down Expand Up @@ -46,6 +46,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
WORKDIR /app
COPY . /app

# Copy alarm sound to /usr/share/sounds
RUN mkdir -p /usr/share/sounds && cp /app/contrib/alarm.wav /usr/share/sounds

# Create virtual environment and install dependencies
RUN python3 -m venv /env && \
/env/bin/pip install --no-cache-dir --use-pep517 -r src/requirements.txt
Expand Down Expand Up @@ -92,7 +95,7 @@ RUN mkdir -p /var/log/supervisor && \
echo 'stdout_logfile=/dev/fd/1'; \
echo 'stdout_logfile_maxbytes=0'; \
echo 'redirect_stderr=true'; \
} > /etc/supervisor/conf.d/supervisord.conf
} > /etc/supervisor/conf.d/supervisord.conf

# Expose the Uvicorn port
EXPOSE 8000
Expand Down
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ This guide will explain how to build your own. It's pretty straight forward. You
✅ Spotify
✅ Philips Hue
✅ OpenWeatherMap
✅ Open-Meteo

</td>
<td>

✅ Open-Meteo
🔲 Alarms
🔲 Reminders
✅ Alarms
✅ Reminders
🔲 LiteLLM
🔲 Calendar (CalDAV)

</td>
</tr>
Expand All @@ -50,10 +51,10 @@ This guide will explain how to build your own. It's pretty straight forward. You
<tr>
<td>
🌈 Weather
🌡️ Temperature
☁️ Weather
🌅 Sunrise/Sunset
📅 Calendar
⏰ Alarms
⌚ Reminders

</td>
<td>
Expand Down Expand Up @@ -436,6 +437,8 @@ If you prefer to run the setup script manually, you can do so. Create a script i
```bash
#!/bin/bash
latest_release=$(curl -s https://api.github.com/repos/judahpaul16/gpt-home/releases/latest | grep 'tag_name' | cut -d\" -f4)
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
Expand All @@ -446,6 +449,13 @@ CYAN='\033[0;36m'
WHITE='\033[0;37m'
NC='\033[0m' # No Color
echo ""
echo -e "${MAGENTA}"
echo "GPT Home $latest_release"
echo "Created by Judah Paul"
echo "More info @ https://github.com/judahpaul16/gpt-home/"
echo -e "${NC}"
echo -e "${GREEN}"
echo " ____ ____ _____ _ _ "
echo " / ___| _ \\_ _| | | | | ___ _ __ ___ ___ "
Expand Down Expand Up @@ -764,14 +774,15 @@ chmod +x setup.sh
<tr>
<td>
- [OpenAI API Docs](https://platform.openai.com/docs/introduction)
- [Raspberry Pi Docs](https://www.raspberrypi.com/documentation)
- [Node.js Docs](https://nodejs.org/en/docs/)
- [npm Docs](https://docs.npmjs.com/)
- [NGINX Docs](https://nginx.org/en/docs/)
- [React Docs](https://reactjs.org/docs/getting-started.html)
- [Docker Docs](https://docs.docker.com/)
- [Docker Buildx Docs](https://docs.docker.com/buildx/working-with-buildx/)
- [FastAPI Docs](https://fastapi.tiangolo.com/)
- [Ubuntu Server Docs](https://ubuntu.com/server/docs)
- [NGINX Docs](https://nginx.org/en/docs/)
- [React Docs](https://reactjs.org/docs/getting-started.html)
- [Node.js Docs](https://nodejs.org/en/docs/)
- [npm Docs](https://docs.npmjs.com/)
</td>
<td>
Expand All @@ -783,7 +794,8 @@ chmod +x setup.sh
- [ALSA Docs](https://www.alsa-project.org/wiki/Documentation)
- [PortAudio Docs](http://www.portaudio.com/docs/v19-doxydocs/index.html)
- [SpeechRecognition Docs](https://pypi.org/project/SpeechRecognition/)
- [Docker Docs](https://docs.docker.com/)
- [OpenAI API Docs](https://platform.openai.com/docs/introduction)
- [LiteLLM Docs](https://docs.litellm.ai/docs/)
</td>
<td>
Expand All @@ -795,6 +807,7 @@ chmod +x setup.sh
- [Phillips Hue Python API Docs](https://github.com/studioimaginaire/phue)
- [OpenWeatherMap API Docs](https://openweathermap.org/api/one-call-3)
- [Open-Meteo API Docs](https://open-meteo.com/en/docs)
- [Python Crontab Docs](https://pypi.org/project/python-crontab/)
- [Fritzing Schematics](https://fritzing.org/)
</td>
Expand Down
Binary file added contrib/alarm.wav
Binary file not shown.
9 changes: 9 additions & 0 deletions contrib/setup.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

latest_release=$(curl -s https://api.github.com/repos/judahpaul16/gpt-home/releases/latest | grep 'tag_name' | cut -d\" -f4)

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
Expand All @@ -10,6 +12,13 @@ CYAN='\033[0;36m'
WHITE='\033[0;37m'
NC='\033[0m' # No Color

echo ""
echo -e "${MAGENTA}"
echo "GPT Home $latest_release"
echo "Created by Judah Paul"
echo "More info @ https://github.com/judahpaul16/gpt-home/"
echo -e "${NC}"

echo -e "${GREEN}"
echo " ____ ____ _____ _ _ "
echo " / ___| _ \\_ _| | | | | ___ _ __ ___ ___ "
Expand Down
Binary file added screenshots/preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 2 additions & 5 deletions src/frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,8 @@ Instead, it will copy all the configuration files and the transitive dependencie

You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.

## 📸 Screenshots
![Integrations](https://github.com/judahpaul16/gpt-home/blob/main/screenshots/integrations.png?raw=true)
![Event Logs](https://github.com/judahpaul16/gpt-home/blob/main/screenshots/event_logs.png?raw=true)
![Settings](https://github.com/judahpaul16/gpt-home/blob/main/screenshots/settings.png?raw=true)
![About](https://github.com/judahpaul16/gpt-home/blob/main/screenshots/about.png?raw=true)
## 📸 Preview
![Integrations](../../screenshots/preview.gif)


## 📚 React Documentation
Expand Down
103 changes: 102 additions & 1 deletion src/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import speech_recognition as sr
from asyncio import create_task
from dotenv import load_dotenv
from threading import Timer
from pathlib import Path
from phue import Bridge
import subprocess
import traceback
import datetime
import textwrap
import requests
import logging
Expand Down Expand Up @@ -546,7 +546,108 @@ async def query_openai(text, display, retries=3):
await handle_error(error_message, None, display)
await asyncio.sleep(0.5) # Wait before retrying

alarms = {}

def set_alarm(command, minute, hour, day_of_month, month, day_of_week, comment):
now = datetime.now()
alarm_time = now.replace(minute=minute, hour=hour, day=day_of_month, month=month, second=0, microsecond=0)

if alarm_time < now:
alarm_time += timedelta(days=1)

delay = (alarm_time - now).total_seconds()
timer = Timer(delay, lambda: subprocess.Popen(command, shell=True))
timer.start()
alarms[comment] = timer
return "Alarm set successfully."

def delete_alarm(comment):
if comment in alarms:
alarms[comment].cancel()
del alarms[comment]
return "Alarm deleted successfully."
else:
return "No such alarm to delete."

def snooze_alarm(comment, snooze_minutes):
if comment in alarms:
alarms[comment].cancel()
del alarms[comment]

now = datetime.now()
snooze_time = now + timedelta(minutes=snooze_minutes)
delay = (snooze_time - now).total_seconds()
command = "aplay /usr/share/sounds/alarm.wav"
timer = Timer(delay, lambda: subprocess.Popen(command, shell=True))
timer.start()
alarms[comment] = timer
return "Alarm snoozed successfully."
else:
return "No such alarm to snooze."

def parse_time_expression(time_expression):
if re.match(r'\d+:\d+', time_expression): # HH:MM format
hour, minute = map(int, time_expression.split(':'))
return minute, hour, '*', '*', '*'
elif re.match(r'\d+\s*minutes?', time_expression): # N minutes from now
minutes = int(re.search(r'\d+', time_expression).group())
now = datetime.now() + timedelta(minutes=minutes)
return now.minute, now.hour, now.day, now.month, '*'
else:
raise ValueError("Invalid time expression")

def set_reminder(command, minute, hour, day_of_month, month, day_of_week, comment):
now = datetime.now()
reminder_time = now.replace(minute=minute, hour=hour, day=day_of_month, month=month, second=0, microsecond=0)

if reminder_time < now:
reminder_time += timedelta(days=1)

delay = (reminder_time - now).total_seconds()
Timer(delay, lambda: subprocess.Popen(command, shell=True)).start()
return "Reminder set successfully."

async def alarm_reminder_action(text):
set_match = re.search(r'\b(?:set|create|schedule)\s+(?:an\s+)?alarm\b.*?\b(?:for|in)\s*(\d{1,2}:\d{2}|\d+\s*(?:minutes?|mins?|hours?|hrs?))\b', text, re.IGNORECASE)
delete_match = re.search(r'\b(?:delete|remove|cancel)\s+(?:an\s+)?alarm\b.*?\b(?:called|named)\s*(\w+)', text, re.IGNORECASE)
snooze_match = re.search(r'\b(?:snooze|delay|postpone)\s+(?:an\s+)?alarm\b.*?\b(?:for|by)\s*(\d+\s*(?:minutes?|mins?))\b', text, re.IGNORECASE)
remind_match = re.search(r'\b(?:remind)\s+(?:me)\s+(?:to|in)\s*(\d+\s*(?:minutes?|mins?|hours?|hrs?))\s+to\s*(.+)', text, re.IGNORECASE)

if set_match:
time_expression = set_match.group(1)
minute, hour, dom, month, dow = parse_time_expression(time_expression)
command = "aplay /usr/share/sounds/alarm.wav"
comment = "Alarm"
return set_alarm(command, minute, hour, dom, month, dow, comment)
elif delete_match:
comment = delete_match.group(1)
return delete_alarm(comment)
elif snooze_match:
snooze_time = snooze_match.group(1)
snooze_minutes = int(re.search(r'\d+', snooze_time).group())
comment = "Alarm"
return snooze_alarm(comment, snooze_minutes)
elif remind_match:
time_expression = remind_match.group(1)
reminder_text = remind_match.group(2)
minute, hour, dom, month, dow = parse_time_expression(time_expression)
command = f"""
bash -c 'source /env/bin/activate && python -c "import pyttsx3;
engine = pyttsx3.init();
engine.setProperty(\\"rate\\", 145);
engine.say(\\"Reminder: {reminder_text}\\");
engine.runAndWait()"'
"""
comment = "Reminder"
return set_reminder(command, minute, hour, dom, month, dow, comment)
else:
return "Invalid command."

async def action_router(text: str, display):
# Alarm and Reminder actions
if re.search(r'\b(set|create|cancel|delete|remove|snooze|dismiss|stop|remind|alarm|timer)\b', text, re.IGNORECASE):
return await alarm_reminder_action(text)

# For Spotify actions
if re.search(r'\b(play|resume|next song|go back|pause|stop|shuffle|repeat|volume)(\s.*)?(\bon\b\sSpotify)?\b', text, re.IGNORECASE):
return await spotify_action(text)
Expand Down

0 comments on commit ffa7d6b

Please sign in to comment.