├── pvporcupine.py ├── resources ├── back.png ├── background.jpg ├── spotify │ ├── banner.png │ ├── menu.png │ ├── pause.png │ ├── play.png │ ├── skip.png │ └── previous.png ├── weather │ └── images │ │ ├── overcast.png │ │ └── partlycloudy.png └── fonts │ └── good-times │ └── good_times_rg.otf ├── apps ├── app_1 │ ├── app_1.png │ ├── JetBrainsMono.ttf │ ├── BebasNeue-Regular.ttf │ ├── themes │ │ ├── stormy │ │ │ ├── back.png │ │ │ └── background.png │ │ └── sunny │ │ │ ├── back.png │ │ │ └── background.png │ ├── JetBrainsMono-ExtraBold.ttf │ ├── __pycache__ │ │ └── app_1.cpython-310.pyc │ └── app_1.py ├── app_2 │ ├── app_2.png │ ├── back.png │ ├── background.jpg │ ├── __pycache__ │ │ └── app_2.cpython-310.pyc │ └── app_2.py ├── app_3 │ ├── app_3.png │ ├── __pycache__ │ │ └── app_3.cpython-310.pyc │ └── app_3.py ├── app_4 │ ├── app_4.png │ ├── back.png │ ├── banner.png │ ├── JetBrainsMono.ttf │ ├── records │ │ ├── record.jpg │ │ ├── record2.jpg │ │ └── record3.jpg │ ├── JetBrainsMono-ExtraBold.ttf │ ├── __pycache__ │ │ └── app_4.cpython-310.pyc │ ├── app_4(old).py │ └── app_4.py ├── app_5 │ ├── app_5.png │ ├── __pycache__ │ │ └── app_5.cpython-310.pyc │ └── app_5.py ├── app_6 │ ├── app_6.png │ ├── pause_button.png │ ├── start_button.png │ ├── stop_button.png │ ├── __pycache__ │ │ └── app_6.cpython-310.pyc │ └── app_6.py ├── app_7 │ ├── app_7.png │ ├── callout.png │ ├── __pycache__ │ │ └── app_7.cpython-310.pyc │ └── app_7.py ├── app_8 │ ├── app_8.png │ ├── __pycache__ │ │ └── app_8.cpython-310.pyc │ └── app_8.py └── __pycache__ │ ├── app_1.cpython-310.pyc │ ├── app_2.cpython-310.pyc │ ├── app_3.cpython-310.pyc │ ├── app_4.cpython-310.pyc │ └── app_5.cpython-310.pyc ├── sample.env ├── main_pod.py ├── tools.py ├── README.md ├── assist.py ├── spot.py └── home_screen.py /pvporcupine.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/back.png -------------------------------------------------------------------------------- /apps/app_1/app_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/app_1.png -------------------------------------------------------------------------------- /apps/app_2/app_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_2/app_2.png -------------------------------------------------------------------------------- /apps/app_2/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_2/back.png -------------------------------------------------------------------------------- /apps/app_3/app_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_3/app_3.png -------------------------------------------------------------------------------- /apps/app_4/app_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/app_4.png -------------------------------------------------------------------------------- /apps/app_4/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/back.png -------------------------------------------------------------------------------- /apps/app_5/app_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_5/app_5.png -------------------------------------------------------------------------------- /apps/app_6/app_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_6/app_6.png -------------------------------------------------------------------------------- /apps/app_7/app_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_7/app_7.png -------------------------------------------------------------------------------- /apps/app_8/app_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_8/app_8.png -------------------------------------------------------------------------------- /apps/app_4/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/banner.png -------------------------------------------------------------------------------- /apps/app_7/callout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_7/callout.png -------------------------------------------------------------------------------- /apps/app_2/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_2/background.jpg -------------------------------------------------------------------------------- /resources/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/background.jpg -------------------------------------------------------------------------------- /apps/app_1/JetBrainsMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/JetBrainsMono.ttf -------------------------------------------------------------------------------- /apps/app_4/JetBrainsMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/JetBrainsMono.ttf -------------------------------------------------------------------------------- /apps/app_6/pause_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_6/pause_button.png -------------------------------------------------------------------------------- /apps/app_6/start_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_6/start_button.png -------------------------------------------------------------------------------- /apps/app_6/stop_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_6/stop_button.png -------------------------------------------------------------------------------- /resources/spotify/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/banner.png -------------------------------------------------------------------------------- /resources/spotify/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/menu.png -------------------------------------------------------------------------------- /resources/spotify/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/pause.png -------------------------------------------------------------------------------- /resources/spotify/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/play.png -------------------------------------------------------------------------------- /resources/spotify/skip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/skip.png -------------------------------------------------------------------------------- /apps/app_4/records/record.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/records/record.jpg -------------------------------------------------------------------------------- /apps/app_4/records/record2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/records/record2.jpg -------------------------------------------------------------------------------- /apps/app_4/records/record3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/records/record3.jpg -------------------------------------------------------------------------------- /resources/spotify/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/spotify/previous.png -------------------------------------------------------------------------------- /apps/app_1/BebasNeue-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/BebasNeue-Regular.ttf -------------------------------------------------------------------------------- /apps/app_1/themes/stormy/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/themes/stormy/back.png -------------------------------------------------------------------------------- /apps/app_1/themes/sunny/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/themes/sunny/back.png -------------------------------------------------------------------------------- /apps/__pycache__/app_1.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/__pycache__/app_1.cpython-310.pyc -------------------------------------------------------------------------------- /apps/__pycache__/app_2.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/__pycache__/app_2.cpython-310.pyc -------------------------------------------------------------------------------- /apps/__pycache__/app_3.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/__pycache__/app_3.cpython-310.pyc -------------------------------------------------------------------------------- /apps/__pycache__/app_4.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/__pycache__/app_4.cpython-310.pyc -------------------------------------------------------------------------------- /apps/__pycache__/app_5.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/__pycache__/app_5.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_1/JetBrainsMono-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/JetBrainsMono-ExtraBold.ttf -------------------------------------------------------------------------------- /apps/app_1/themes/sunny/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/themes/sunny/background.png -------------------------------------------------------------------------------- /apps/app_4/JetBrainsMono-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/JetBrainsMono-ExtraBold.ttf -------------------------------------------------------------------------------- /resources/weather/images/overcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/weather/images/overcast.png -------------------------------------------------------------------------------- /apps/app_1/themes/stormy/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/themes/stormy/background.png -------------------------------------------------------------------------------- /resources/weather/images/partlycloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/weather/images/partlycloudy.png -------------------------------------------------------------------------------- /apps/app_1/__pycache__/app_1.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_1/__pycache__/app_1.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_2/__pycache__/app_2.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_2/__pycache__/app_2.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_3/__pycache__/app_3.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_3/__pycache__/app_3.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_4/__pycache__/app_4.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_4/__pycache__/app_4.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_5/__pycache__/app_5.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_5/__pycache__/app_5.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_6/__pycache__/app_6.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_6/__pycache__/app_6.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_7/__pycache__/app_7.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_7/__pycache__/app_7.cpython-310.pyc -------------------------------------------------------------------------------- /apps/app_8/__pycache__/app_8.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/apps/app_8/__pycache__/app_8.cpython-310.pyc -------------------------------------------------------------------------------- /resources/fonts/good-times/good_times_rg.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/HomePod/HEAD/resources/fonts/good-times/good_times_rg.otf -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | WEATHER_API_KEY= 2 | WEATHER_URL=http://api.weatherapi.com/v1/current.json 3 | WEATHER_CITY=Chicago 4 | #Spotify 5 | SPOTIFY_USERNAME='' 6 | SPOTIFY_CLIENT_ID='' 7 | SPOTIFY_CLIENT_SECRET='' 8 | SPOTIFY_REDIRECT_URI=http://localhost:8888/callback 9 | #Jarvis assistant 10 | API_KEY = "" 11 | ASSISTANT_ID = "" 12 | THREAD_ID = "" 13 | #stock app 14 | STOCK_SYMBOLS=AAPL,GOOGL,MSFT,AMZN,TSLA 15 | #SPORTS 16 | SPORTS=SOCCER,MLB,NHL,NFL,NBA,WNBA 17 | #Smart Home 18 | SMARTTHINGS_API_TOKEN= 19 | -------------------------------------------------------------------------------- /main_pod.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from home_screen import run_home_screen 3 | 4 | def main(): 5 | pygame.init() 6 | # Use the current screen resolution for fullscreen mode 7 | info = pygame.display.Info() 8 | screen_width, screen_height = info.current_w, info.current_h 9 | screen = pygame.display.set_mode((screen_width, screen_height), pygame.FULLSCREEN) 10 | pygame.display.set_caption('Home Screen with App Circles') 11 | run_home_screen(screen) 12 | 13 | if __name__ == '__main__': 14 | main() 15 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | import python_weather 2 | import asyncio 3 | import assist 4 | from icrawler.builtin import GoogleImageCrawler 5 | import os 6 | import spot 7 | 8 | 9 | async def get_weather(city_name): 10 | async with python_weather.Client(unit=python_weather.IMPERIAL) as client: 11 | weather = await client.get(city_name) 12 | return weather 13 | 14 | def search(query): 15 | google_Crawler = GoogleImageCrawler(storage = {"root_dir": r'./images'}) 16 | google_Crawler.crawl(keyword = query, max_num = 1) 17 | 18 | 19 | def parse_command(command): 20 | if "weather" in command: 21 | weather_description = asyncio.run(get_weather("Chicago")) 22 | query = "System information: " + str(weather_description) 23 | print(query) 24 | response = assist.ask_question_memory(query) 25 | done = assist.TTS(response) 26 | 27 | if "search" in command: 28 | files = os.listdir("./images") 29 | [os.remove(os.path.join("./images", f))for f in files] 30 | query = command.split("-")[1] 31 | search(query) 32 | 33 | if "play" in command: 34 | spot.start_music() 35 | 36 | if "pause" in command: 37 | spot.stop_music() 38 | 39 | if "skip" in command: 40 | spot.skip_to_next() 41 | 42 | if "previous" in command: 43 | spot.skip_to_previous() 44 | 45 | if "spotify" in command: 46 | spotify_info = spot.get_current_playing_info() 47 | query = "System information: " + str(spotify_info) 48 | print(query) 49 | response = assist.ask_question_memory(query) 50 | done = assist.TTS(response) 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Home Pod Voice Assistant 2 | 3 | The Home Pod is an LLM voice assistant that runs on a raspberry pi. This applictaion has a display and user interface with apps for weather, sports, spotify, and more. 4 | 5 | ## Prerequisites 6 | 7 | Before you begin, ensure you have the following: 8 | - A Raspberry Pi set up with internet access. 9 | - An OpenAI API key, Assistant ID, and Thread ID. You can obtain these from your OpenAI account on the [OpenAI Platform](https://platform.openai.com/assistants). 10 | 11 | ## Installation 12 | 13 | 1. **Clone the Repository** 14 | 15 | Open a terminal on your Raspberry Pi and run the following command to clone the repository: 16 | 17 | ```git clone https://github.com/Concept-Bytes/HomePod.git``` 18 | 19 | Navigate into the project directory: 20 | 21 | 2. **Install Dependencies** 22 | 23 | Install the required Python packages: 24 | 25 | 3. **Configuration** 26 | 27 | Open `sample.env` in a text editor of your choice. Fill in your OpenAI API key, Assistant ID, and Thread ID in the designated spots. This information is critical for the AI functionality of the project and can be obtained from your OpenAI account. 28 | 29 | Rename this to just .env 30 | 31 | ```python 32 | # Example placeholder in .env 33 | API_KEY = "your_openai_api_key_here" 34 | ASSISTANT_ID = "your_assistant_id_here" 35 | THREAD_ID = "your_thread_id_here" 36 | 37 | 38 | ## Running the Application 39 | 40 | After completing the setup and configuration, run the application with: 41 | 42 | ```python homescreen.py``` 43 | 44 | This script will run the user interface in window mode to run this full screen run: 45 | 46 | ```python main_pod.py``` 47 | 48 | 49 | ## Contributing 50 | Contributions to the Home Pod are welcome! Please feel free to fork the repository, make your changes, and submit a pull request. 51 | 52 | ## License 53 | This project is licensed under the MIT License - see the LICENSE file in the repository for details. 54 | # HomePod 55 | -------------------------------------------------------------------------------- /assist.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | import time 3 | from pygame import mixer 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | # Load environment variables from .env file 8 | load_dotenv() 9 | 10 | #https://platform.openai.com/playground/assistants 11 | # Initialize the client and mixer 12 | client = OpenAI(default_headers={"OpenAI-Beta": "assistants=v2"}, api_key= os.getenv('API_KEY')) 13 | mixer.init() 14 | 15 | 16 | 17 | # Retrieve the assistant and thread 18 | assistant = client.beta.assistants.retrieve(os.getenv('ASSISTANT_ID')) 19 | thread = client.beta.threads.retrieve(os.getenv('THREAD_ID')) 20 | 21 | def ask_question_memory(question): 22 | global thread 23 | client.beta.threads.messages.create(thread.id, role="user", content=question) 24 | run = client.beta.threads.runs.create(thread_id=thread.id, assistant_id=assistant.id) 25 | 26 | while (run_status := client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)).status != 'completed': 27 | if run_status.status == 'failed': 28 | return "The run failed." 29 | time.sleep(1) 30 | 31 | messages = client.beta.threads.messages.list(thread_id=thread.id) 32 | return messages.data[0].content[0].text.value 33 | 34 | def generate_tts(sentence, speech_file_path): 35 | response = client.audio.speech.create(model="tts-1", voice="echo", input=sentence) 36 | response.stream_to_file(speech_file_path) 37 | return str(speech_file_path) 38 | 39 | def play_sound(file_path): 40 | mixer.music.load(file_path) 41 | mixer.music.play() 42 | 43 | def TTS(text): 44 | speech_file_path = generate_tts(text, "speech.mp3") 45 | play_sound(speech_file_path) 46 | while mixer.music.get_busy(): 47 | time.sleep(1) 48 | mixer.music.unload() 49 | os.remove(speech_file_path) 50 | return "done" 51 | 52 | # question = "make it slightly vary every time" 53 | # response = ask_question_memory(question) 54 | # print(response) -------------------------------------------------------------------------------- /spot.py: -------------------------------------------------------------------------------- 1 | import spotipy 2 | from spotipy.oauth2 import SpotifyOAuth 3 | from dotenv import load_dotenv 4 | import os 5 | 6 | load_dotenv() 7 | 8 | username = os.getenv('SPOTIFY_USERNAME') 9 | clientID = os.getenv('SPOTIFY_CLIENT_ID') 10 | clientSecret = os.getenv('SPOTIFY_CLIENT_SECRET') 11 | redirect_uri = 'http://localhost:8888/callback' 12 | 13 | def spotify_authenicate(client_id, client_secret, redirect_uri, username): 14 | scope = "user-read-currently-playing user-modify-playback-state" 15 | auth_manager = SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope, username=username) 16 | return spotipy.Spotify(auth_manager = auth_manager) 17 | 18 | spotify = spotify_authenicate(clientID, clientSecret, redirect_uri, username) 19 | 20 | def get_current_playing_info(): 21 | global spotify 22 | current_track = spotify.current_user_playing_track() 23 | if current_track is None: 24 | return None 25 | 26 | artist_name = current_track['item']['artists'][0]['name'] 27 | album_name = current_track['item']['album']['name'] 28 | track_title = current_track['item']['name'] 29 | 30 | return { 31 | "artist": artist_name, 32 | "album": album_name, 33 | "title": track_title 34 | } 35 | 36 | 37 | def start_music(): 38 | global spotify 39 | try: 40 | spotify.start_playback() 41 | except spotipy.SpotifyException as e: 42 | return f"Error in starting playback: {str(e)}" 43 | 44 | def stop_music(): 45 | global spotify 46 | try: 47 | spotify.pause_playback() 48 | except spotipy.SpotifyException as e: 49 | return f"Error in starting playback: {str(e)}" 50 | 51 | def skip_to_next(): 52 | global spotify 53 | try: 54 | spotify.next_track() 55 | except spotipy.SpotifyException as e: 56 | return f"Error in starting playback: {str(e)}" 57 | 58 | def skip_to_previous(): 59 | global spotify 60 | try: 61 | spotify.previous_track() 62 | except spotipy.SpotifyException as e: 63 | return f"Error in starting playback: {str(e)}" 64 | 65 | -------------------------------------------------------------------------------- /apps/app_2/app_2.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import sys 4 | from datetime import datetime 5 | 6 | def run(screen): 7 | CLOCK_RADIUS = 540 8 | CENTER = (screen.get_width() // 2, screen.get_height() // 2) 9 | DATE_POS = (CENTER[0], CENTER[1] + 200) # Position for the date, slightly below center 10 | BACK_BUTTON_POS = (CENTER[0], screen.get_height() - 100) # Position for the back button, further below the date 11 | BACK_BUTTON_RADIUS = 40 # Radius for the back button 12 | WHITE = (255, 255, 255) 13 | BLACK = (0, 0, 0) 14 | RED = (255, 0, 0) 15 | HAND_THICKNESS = {'hour': 24, 'minute': 16, 'second': 8} 16 | HAND_LENGTH = {'hour': 350, 'minute': 450, 'second': 500} 17 | FONT_SIZE = 48 18 | DATE_FONT_SIZE = 36 # Smaller font size for the date 19 | font = pygame.font.Font(None, FONT_SIZE) 20 | date_font = pygame.font.Font(None, DATE_FONT_SIZE) 21 | back_button_image_path = './apps/app_2/back.png' # Path to the back button image 22 | back_button_image = pygame.image.load(back_button_image_path) 23 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 24 | background_image_path = './apps/app_2/background.jpg' 25 | background_image = pygame.image.load(background_image_path) 26 | background_image = pygame.transform.scale(background_image, (screen.get_width(), screen.get_height())) 27 | 28 | def draw_hand(angle, length, thickness, color): 29 | angle = math.radians(450 - angle) 30 | end_x = CENTER[0] + length * math.cos(angle) 31 | end_y = CENTER[1] - length * math.sin(angle) 32 | pygame.draw.line(screen, color, CENTER, (int(end_x), int(end_y)), thickness) 33 | 34 | def draw_clock_numbers_and_dots(): 35 | for num in range(1, 12): 36 | angle = math.radians(450 - (num * 30)) 37 | x = CENTER[0] + (CLOCK_RADIUS - 70) * math.cos(angle) 38 | y = CENTER[1] - (CLOCK_RADIUS - 70) * math.sin(angle) 39 | if num in [12, 3, 6, 9]: 40 | text = font.render(str(num), True, WHITE) 41 | text_rect = text.get_rect(center=(int(x), int(y))) 42 | screen.blit(text, text_rect) 43 | else: 44 | pygame.draw.circle(screen, WHITE, (int(x), int(y)), 5) 45 | 46 | def draw_date(): 47 | today = datetime.now() 48 | date_str = today.strftime("%A, %B %d") # E.g., "Monday, April 22" 49 | text = date_font.render(date_str, True, WHITE) 50 | text_rect = text.get_rect(center=DATE_POS) 51 | screen.blit(text, text_rect) 52 | 53 | def draw_back_button(): 54 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 55 | screen.blit(back_button_image, top_left) 56 | 57 | def draw_clock(): 58 | screen.blit(background_image, (0, 0)) 59 | pygame.draw.circle(screen, WHITE, CENTER, CLOCK_RADIUS, 4) 60 | pygame.draw.circle(screen, WHITE, CENTER, 4) 61 | 62 | now = datetime.now() 63 | hour_angle = (now.hour % 12) / 12 * 360 + (now.minute / 60) * 30 64 | minute_angle = now.minute / 60 * 360 + now.second / 60 * 6 65 | second_angle = now.second / 60 * 360 66 | 67 | draw_hand(hour_angle, HAND_LENGTH['hour'], HAND_THICKNESS['hour'], WHITE) 68 | draw_hand(minute_angle, HAND_LENGTH['minute'], HAND_THICKNESS['minute'], WHITE) 69 | draw_hand(second_angle, HAND_LENGTH['second'], HAND_THICKNESS['second'], RED) 70 | draw_clock_numbers_and_dots() 71 | draw_date() 72 | draw_back_button() 73 | 74 | pygame.display.flip() 75 | 76 | running = True 77 | clock = pygame.time.Clock() 78 | while running: 79 | for event in pygame.event.get(): 80 | if event.type == pygame.QUIT: 81 | pygame.quit() 82 | sys.exit() 83 | elif event.type == pygame.MOUSEBUTTONDOWN: 84 | if math.hypot(event.pos[0] - BACK_BUTTON_POS[0], event.pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 85 | return # Return to the home screen if back button is clicked 86 | elif event.type == pygame.KEYDOWN: 87 | if event.key == pygame.K_ESCAPE: 88 | return # Return to the home screen on ESC key 89 | 90 | draw_clock() 91 | clock.tick(60) 92 | -------------------------------------------------------------------------------- /apps/app_3/app_3.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import samsung 4 | 5 | def run(screen): 6 | NUM_DEVICES = 12 7 | RADIUS = 100 8 | DEVICE_NAMES = ["lamp", "lamp2"] + ["Device " + str(i) for i in range(3, NUM_DEVICES+1)] 9 | device_states = {name: False for name in DEVICE_NAMES} 10 | CENTER = (screen.get_width() // 2, screen.get_height() // 2) 11 | DEVICE_CIRCLE_RADIUS = 425 # Radius of the circle on which device circles are placed 12 | BACK_BUTTON_RADIUS = 50 # Radius for the back button 13 | BACK_BUTTON_POS = CENTER 14 | FONT_SIZE = 24 15 | font = pygame.font.Font(None, FONT_SIZE) 16 | BLACK = (0, 0, 0) 17 | WHITE = (255, 255, 255) 18 | 19 | background_image_path = './apps/app_2/background.jpg' 20 | background_image = pygame.image.load(background_image_path) 21 | background_image = pygame.transform.scale(background_image, (screen.get_width(), screen.get_height())) 22 | 23 | back_button_image_path = './resources/back.png' 24 | back_button_image = pygame.image.load(back_button_image_path) 25 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 26 | 27 | def draw_device_circle(angle, name, on): 28 | angle = math.radians(angle) 29 | x = CENTER[0] + DEVICE_CIRCLE_RADIUS * math.cos(angle) 30 | y = CENTER[1] + DEVICE_CIRCLE_RADIUS * math.sin(angle) 31 | circle_inner_color = (255, 255, 255, 128) if on else (0, 0, 0, 128) # Semi-transparent white if on, semi-transparent black if off 32 | text_color = BLACK if on else WHITE # Black text if on, white if off 33 | 34 | # Create a surface for the circle with per-pixel alpha 35 | circle_surface = pygame.Surface((RADIUS * 2, RADIUS * 2), pygame.SRCALPHA) 36 | circle_surface = circle_surface.convert_alpha() 37 | 38 | # Draw the semi-transparent circle 39 | pygame.draw.circle(circle_surface, circle_inner_color, (RADIUS, RADIUS), RADIUS - 4, 0) # Smaller inner circle with transparency 40 | screen.blit(circle_surface, (x - RADIUS, y - RADIUS)) 41 | 42 | # Draw the white border around the circle 43 | pygame.draw.circle(screen, WHITE, (int(x), int(y)), RADIUS, 4) # White border for visibility 44 | 45 | text = font.render(name, True, text_color) 46 | text_rect = text.get_rect(center=(int(x), int(y))) 47 | screen.blit(text, text_rect) 48 | 49 | def draw_back_button(): 50 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 51 | screen.blit(back_button_image, top_left) 52 | 53 | def toggle_device(name): 54 | current_state = device_states[name] 55 | new_state = not current_state 56 | device_states[name] = new_state 57 | samsung.command_device_sync(name, new_state) 58 | print(f"Command sent to {name}: {'On' if new_state else 'Off'}") # Mocking command 59 | 60 | def check_click(pos): 61 | # Check back button click 62 | if math.hypot(pos[0] - BACK_BUTTON_POS[0], pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 63 | return False # Return False to indicate that the app should close 64 | # Check device circle clicks 65 | for i, name in enumerate(DEVICE_NAMES): 66 | angle = 360 / NUM_DEVICES * i 67 | angle = math.radians(angle) 68 | x = CENTER[0] + DEVICE_CIRCLE_RADIUS * math.cos(angle) 69 | y = CENTER[1] + DEVICE_CIRCLE_RADIUS * math.sin(angle) 70 | if math.sqrt((pos[0] - x) ** 2 + (pos[1] - y) ** 2) <= RADIUS: 71 | toggle_device(name) 72 | break 73 | return True 74 | 75 | running = True 76 | while running: 77 | for event in pygame.event.get(): 78 | if event.type == pygame.QUIT: 79 | running = False 80 | elif event.type == pygame.MOUSEBUTTONDOWN: 81 | running = check_click(pygame.mouse.get_pos()) 82 | 83 | screen.blit(background_image, (0, 0)) 84 | for i, name in enumerate(DEVICE_NAMES): 85 | draw_device_circle(360 / NUM_DEVICES * i, name, device_states[name]) 86 | draw_back_button() 87 | pygame.display.flip() 88 | 89 | if __name__ == "__main__": 90 | pygame.init() 91 | screen = pygame.display.set_mode((1080, 1080)) 92 | pygame.display.set_caption("Smart Home Control") 93 | run(screen) 94 | pygame.quit() 95 | -------------------------------------------------------------------------------- /apps/app_5/app_5.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import yfinance as yf 3 | import os 4 | import time 5 | from dotenv import load_dotenv 6 | import math 7 | 8 | # Load environment variables from .env file 9 | load_dotenv() 10 | 11 | # Read stock symbols from environment variables 12 | stock_symbols = os.getenv('STOCK_SYMBOLS').split(',') 13 | 14 | # Initialize Pygame 15 | pygame.init() 16 | pygame.display.set_caption('Stock Ticker App') 17 | clock = pygame.time.Clock() 18 | 19 | BLACK = (0, 0, 0) 20 | WHITE = (255, 255, 255) 21 | GREEN = (0, 255, 0) 22 | RED = (255, 0, 0) 23 | 24 | def load_custom_font(path, size): 25 | """Loads a custom font.""" 26 | try: 27 | return pygame.font.Font(path, size) 28 | except Exception as e: 29 | print(f"Failed to load font: {e}") 30 | return pygame.font.Font(None, size) # Default font if custom font fails 31 | 32 | # Path to the custom font 33 | font_path = './resources/fonts/good-times/good_times_rg.otf' 34 | font_small = load_custom_font(font_path, 30) 35 | font_large = load_custom_font(font_path, 200) 36 | font_symbol = load_custom_font(font_path, 120) 37 | 38 | def get_stock_data(symbol): 39 | """Fetches stock data using the yfinance library.""" 40 | stock = yf.Ticker(symbol) 41 | current_price = stock.history(period="1d")['Close'].iloc[-1] 42 | previous_price = stock.history(period="2d")['Close'].iloc[0] 43 | increase_dollars = current_price - previous_price 44 | return current_price, increase_dollars 45 | 46 | def run(screen): 47 | screen_width, screen_height = screen.get_size() 48 | CENTER = (screen_width // 2, screen_height // 2) 49 | BACK_BUTTON_POS = (screen_width // 2, screen_height * 9 // 10) 50 | BACK_BUTTON_RADIUS = 40 51 | rotate_interval = 10 # seconds 52 | fade_speed = 5 53 | 54 | background_image_path = './apps/app_2/background.jpg' 55 | background_image = pygame.image.load(background_image_path) 56 | background_image = pygame.transform.scale(background_image, (screen_width, screen_height)) 57 | 58 | back_button_image_path = './resources/back.png' 59 | back_button_image = pygame.image.load(back_button_image_path) 60 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 61 | 62 | stock_index = 0 63 | alpha = 255 64 | fade_in = True 65 | fade_out = False 66 | start_time = time.time() 67 | 68 | def draw_back_button(): 69 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 70 | screen.blit(back_button_image, top_left) 71 | 72 | running = True 73 | while running: 74 | screen.blit(background_image, (0, 0)) 75 | draw_back_button() 76 | 77 | current_time = time.time() 78 | if current_time - start_time > rotate_interval: 79 | fade_out = True 80 | fade_in = False 81 | 82 | symbol = stock_symbols[stock_index] 83 | try: 84 | price, gain_dollars = get_stock_data(symbol) 85 | color = GREEN if gain_dollars > 0 else RED 86 | price_text = f"${price:.2f}" 87 | symbol_text = symbol 88 | except Exception as e: 89 | color = BLACK 90 | price_text = f"Error fetching data" 91 | symbol_text = symbol 92 | 93 | if fade_in: 94 | alpha += fade_speed 95 | if alpha >= 255: 96 | alpha = 255 97 | fade_in = False 98 | elif fade_out: 99 | alpha -= fade_speed 100 | if alpha <= 0: 101 | alpha = 0 102 | fade_out = False 103 | fade_in = True 104 | start_time = current_time 105 | stock_index = (stock_index + 1) % len(stock_symbols) 106 | 107 | # Render and display symbol text 108 | symbol_surface = font_symbol.render(symbol_text, True, WHITE) 109 | symbol_surface.set_alpha(alpha) 110 | symbol_rect = symbol_surface.get_rect(center=(CENTER[0], CENTER[1] - 150)) 111 | screen.blit(symbol_surface, symbol_rect) 112 | 113 | # Render and display price text 114 | price_surface = font_large.render(price_text, True, color) 115 | price_surface.set_alpha(alpha) 116 | price_rect = price_surface.get_rect(center=CENTER) 117 | screen.blit(price_surface, price_rect) 118 | 119 | for event in pygame.event.get(): 120 | if event.type == pygame.QUIT: 121 | pygame.quit() 122 | sys.exit() 123 | elif event.type == pygame.MOUSEBUTTONDOWN: 124 | if math.hypot(event.pos[0] - BACK_BUTTON_POS[0], event.pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 125 | running = False # Exit the loop if back button is pressed 126 | 127 | pygame.display.flip() 128 | clock.tick(30) 129 | 130 | if __name__ == "__main__": 131 | pygame.init() 132 | screen = pygame.display.set_mode((1080, 1080)) 133 | pygame.display.set_caption("Stock Ticker App") 134 | run(screen) 135 | pygame.quit() 136 | -------------------------------------------------------------------------------- /apps/app_7/app_7.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import requests 3 | import time 4 | import math 5 | import sys 6 | 7 | def get_news(): 8 | query_params = { 9 | "source": "bbc-news", 10 | "sortBy": "top", 11 | "apiKey": "4dbc17e007ab436fb66416009dfb59a8" 12 | } 13 | main_url = "https://newsapi.org/v1/articles" 14 | res = requests.get(main_url, params=query_params) 15 | open_bbc_page = res.json() 16 | article = open_bbc_page["articles"] 17 | headlines = [ar["title"] for ar in article] 18 | return headlines[:10] # Get only the top 10 headlines 19 | 20 | def wrap_text(text, font, max_width): 21 | words = text.split(' ') 22 | lines = [] 23 | current_line = "" 24 | 25 | for word in words: 26 | test_line = current_line + word + " " 27 | if font.size(test_line)[0] <= max_width: 28 | current_line = test_line 29 | else: 30 | lines.append(current_line.strip()) 31 | current_line = word + " " 32 | 33 | lines.append(current_line.strip()) 34 | return lines 35 | 36 | def run(screen): 37 | pygame.font.init() 38 | FONT_SIZE = 64 39 | font = pygame.font.Font(None, FONT_SIZE) 40 | fade_speed = 5 # Speed of fade in and fade out 41 | 42 | CENTER = (screen.get_width() // 2, screen.get_height() // 2) 43 | BACK_BUTTON_POS = (CENTER[0], screen.get_height() - 100) 44 | BACK_BUTTON_RADIUS = 40 45 | 46 | WHITE = (255, 255, 255) 47 | BLACK = (0, 0, 0) 48 | 49 | back_button_image_path = './resources/back.png' 50 | back_button_image = pygame.image.load(back_button_image_path) 51 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 52 | 53 | background_image_path = './apps/app_2/background.jpg' 54 | background_image = pygame.image.load(background_image_path) 55 | background_image = pygame.transform.scale(background_image, (screen.get_width(), screen.get_height())) 56 | 57 | callout_image_path = './apps/app_7/callout.png' 58 | callout_image = pygame.image.load(callout_image_path) 59 | 60 | app_image_path = './apps/app_7/app_7.png' 61 | app_image = pygame.image.load(app_image_path) 62 | app_image = pygame.transform.scale(app_image, (200, 200)) 63 | 64 | headlines = get_news() 65 | headline_index = 0 66 | alpha = 255 67 | fade_in = True 68 | fade_out = False 69 | display_time = 20 # seconds 70 | start_time = time.time() 71 | 72 | def draw_back_button(): 73 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 74 | screen.blit(back_button_image, top_left) 75 | 76 | running = True 77 | clock = pygame.time.Clock() 78 | while running: 79 | for event in pygame.event.get(): 80 | if event.type == pygame.QUIT: 81 | pygame.quit() 82 | sys.exit() 83 | elif event.type == pygame.MOUSEBUTTONDOWN: 84 | if math.hypot(event.pos[0] - BACK_BUTTON_POS[0], event.pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 85 | return # Return to the home screen if the back button is clicked 86 | elif event.type == pygame.KEYDOWN: 87 | if event.key == pygame.K_ESCAPE: 88 | return # Return to the home screen on ESC key 89 | 90 | current_time = time.time() 91 | if current_time - start_time > display_time: 92 | fade_out = True 93 | fade_in = False 94 | 95 | screen.blit(background_image, (0, 0)) 96 | screen.blit(app_image, (CENTER[0] - 100, 50)) # Position the app image at the top center 97 | draw_back_button() 98 | 99 | headline_text = headlines[headline_index] 100 | wrapped_text = wrap_text(headline_text, font, screen.get_width() - 100) 101 | line_height = font.size('Tg')[1] 102 | total_text_height = line_height * len(wrapped_text) 103 | 104 | if fade_in: 105 | alpha += fade_speed 106 | if alpha >= 255: 107 | alpha = 255 108 | fade_in = False 109 | elif fade_out: 110 | alpha -= fade_speed 111 | if alpha <= 0: 112 | alpha = 0 113 | fade_out = False 114 | fade_in = True 115 | start_time = current_time 116 | headline_index = (headline_index + 1) % len(headlines) 117 | 118 | callout_image.set_alpha(alpha) 119 | screen.blit(callout_image, (0, 0)) 120 | 121 | for i, line in enumerate(wrapped_text): 122 | text_surface = font.render(line, True, WHITE) 123 | text_surface.set_alpha(alpha) 124 | text_rect = text_surface.get_rect(center=(CENTER[0], CENTER[1] - total_text_height // 2 + i * line_height)) 125 | screen.blit(text_surface, text_rect) 126 | 127 | pygame.display.flip() 128 | clock.tick(30) 129 | 130 | if __name__ == "__main__": 131 | pygame.init() 132 | screen = pygame.display.set_mode((1080, 1080)) 133 | pygame.display.set_caption("News App") 134 | run(screen) 135 | pygame.quit() 136 | -------------------------------------------------------------------------------- /apps/app_6/app_6.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import sys 4 | from datetime import timedelta 5 | 6 | def run(screen): 7 | RING_THICKNESS = 20 8 | CENTER = (screen.get_width() // 2, screen.get_height() // 2) 9 | RING_RADIUS = screen.get_width() // 2 - RING_THICKNESS // 2 10 | BUTTON_POS = (CENTER[0], CENTER[1] + 200) 11 | BUTTON_RADIUS = 50 12 | BACK_BUTTON_POS = (CENTER[0], screen.get_height() - 100) 13 | BACK_BUTTON_RADIUS = 40 14 | WHITE = (255, 255, 255) 15 | RED = (255, 0, 0) 16 | BLUE = (0, 0, 255) 17 | FONT_SIZE = 144 # Increased font size for the time display 18 | font = pygame.font.Font(None, FONT_SIZE) 19 | button_images = { 20 | 'start': pygame.image.load('./apps/app_6/start_button.png'), 21 | 'pause': pygame.image.load('./apps/app_6/pause_button.png'), 22 | 'stop': pygame.image.load('./apps/app_6/stop_button.png') 23 | } 24 | for key in button_images: 25 | button_images[key] = pygame.transform.scale(button_images[key], (2 * BUTTON_RADIUS, 2 * BUTTON_RADIUS)) 26 | back_button_image = pygame.image.load('./resources/back.png') 27 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 28 | current_button = 'start' 29 | timer_running = False 30 | timer_paused = False 31 | timer_duration = timedelta() 32 | remaining_time = timedelta() 33 | 34 | background_image_path = './apps/app_2/background.jpg' 35 | background_image = pygame.image.load(background_image_path) 36 | background_image = pygame.transform.scale(background_image, (screen.get_width(), screen.get_height())) 37 | 38 | def draw_ring(angle, color): 39 | pygame.draw.arc(screen, color, 40 | (RING_THICKNESS // 2, RING_THICKNESS // 2, screen.get_width() - RING_THICKNESS, screen.get_height() - RING_THICKNESS), 41 | math.radians(90), math.radians(90 - angle), RING_THICKNESS) 42 | 43 | def draw_time(): 44 | time_left = str(remaining_time).split('.')[0] 45 | color = RED if timer_running and remaining_time <= timedelta(seconds=timer_duration.total_seconds() * 0.1) else WHITE 46 | text = font.render(time_left, True, color) 47 | text_rect = text.get_rect(center=CENTER) 48 | screen.blit(text, text_rect) 49 | 50 | def draw_button(): 51 | screen.blit(button_images[current_button], (BUTTON_POS[0] - BUTTON_RADIUS, BUTTON_POS[1] - BUTTON_RADIUS)) 52 | 53 | def draw_back_button(): 54 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 55 | screen.blit(back_button_image, top_left) 56 | 57 | def update_time(): 58 | nonlocal remaining_time, timer_running 59 | if timer_running: 60 | remaining_time -= timedelta(seconds=1) 61 | if remaining_time <= timedelta(): 62 | remaining_time = timedelta() 63 | timer_running = False 64 | 65 | def get_angle_from_position(pos): 66 | x, y = pos 67 | angle = (math.degrees(math.atan2(y - CENTER[1], x - CENTER[0])) + 90) % 360 68 | return angle 69 | 70 | running = True 71 | dragging = False 72 | clock = pygame.time.Clock() 73 | while running: 74 | for event in pygame.event.get(): 75 | if event.type == pygame.QUIT: 76 | return # Return to the home screen if the window is closed 77 | elif event.type == pygame.MOUSEBUTTONDOWN: 78 | if math.hypot(event.pos[0] - BUTTON_POS[0], event.pos[1] - BUTTON_POS[1]) <= BUTTON_RADIUS: 79 | if current_button == 'start': 80 | timer_running = True 81 | timer_paused = False 82 | current_button = 'pause' 83 | timer_duration = remaining_time if remaining_time else timedelta(hours=1) 84 | remaining_time = timer_duration 85 | elif current_button == 'pause': 86 | timer_running = False 87 | timer_paused = True 88 | current_button = 'start' 89 | elif current_button == 'stop': 90 | timer_running = False 91 | timer_paused = False 92 | current_button = 'start' 93 | remaining_time = timedelta() 94 | elif math.hypot(event.pos[0] - BACK_BUTTON_POS[0], event.pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 95 | return # Return to the home screen if the back button is clicked 96 | else: 97 | dragging = True 98 | angle = get_angle_from_position(event.pos) 99 | timer_duration = timedelta(seconds=((360 - angle) / 360) * 3600) 100 | timer_duration -= timedelta(seconds=timer_duration.seconds % 60) # Round to the nearest minute 101 | remaining_time = timer_duration 102 | elif event.type == pygame.MOUSEBUTTONUP: 103 | dragging = False 104 | elif event.type == pygame.MOUSEMOTION and dragging and not timer_running: 105 | angle = get_angle_from_position(event.pos) 106 | timer_duration = timedelta(seconds=((360 - angle) / 360) * 3600) 107 | timer_duration -= timedelta(seconds=timer_duration.seconds % 60) # Round to the nearest minute 108 | remaining_time = timer_duration 109 | elif event.type == pygame.KEYDOWN: 110 | if event.key == pygame.K_ESCAPE: 111 | return # Return to the home screen on ESC key 112 | 113 | screen.blit(background_image, (0, 0)) 114 | angle = (360 * (1 - remaining_time.total_seconds() / 3600)) if timer_running else (360 * (1 - timer_duration.total_seconds() / 3600)) 115 | ring_color = RED if timer_running and remaining_time <= timedelta(seconds=timer_duration.total_seconds() * 0.1) else BLUE 116 | draw_ring(angle, ring_color) 117 | draw_time() 118 | draw_button() 119 | draw_back_button() 120 | pygame.display.flip() 121 | 122 | if timer_running: 123 | update_time() 124 | clock.tick(1) 125 | -------------------------------------------------------------------------------- /apps/app_1/app_1.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import sys 4 | import requests 5 | import time 6 | from io import BytesIO 7 | import os 8 | from dotenv import load_dotenv 9 | import assist 10 | 11 | # Load environment variables from .env file 12 | load_dotenv() 13 | 14 | api_key = os.getenv('WEATHER_API_KEY') 15 | url = f"http://api.weatherapi.com/v1/current.json?key={api_key}&q={os.getenv('WEATHER_CITY')}" 16 | themes_dir = './apps/app_1/themes' 17 | font_path = './apps/app_1/JetBrainsMono.ttf' 18 | font_path_extra_bold = './apps/app_1/BebasNeue-Regular.ttf' 19 | 20 | icon_cache = {} 21 | 22 | def get_weather(): 23 | print("GETTING WEATHER") 24 | response = requests.get(url) 25 | if response.status_code == 200: 26 | return response.json() 27 | else: 28 | return {"error": f"Failed to retrieve data: {response.status_code}"} 29 | 30 | def get_icon(url): 31 | if url not in icon_cache: 32 | response = requests.get(url) 33 | if response.status_code == 200: 34 | icon_image = pygame.image.load(BytesIO(response.content)) 35 | icon_cache[url] = pygame.transform.scale(icon_image, (64, 64)) 36 | else: 37 | print(f"Failed to download icon: {url}") 38 | return None 39 | return icon_cache[url] 40 | 41 | # Load custom font 42 | def load_custom_font(path, size): 43 | try: 44 | return pygame.font.Font(path, size) 45 | except Exception as e: 46 | print(f"Failed to load font: {e}") 47 | return pygame.font.Font(None, size) # Default font if custom font fails 48 | 49 | def load_theme(theme): 50 | print(theme) 51 | theme_path = os.path.join(themes_dir, theme) 52 | if not os.path.exists(theme_path): 53 | if "rain" in theme: 54 | theme_path = os.path.join(themes_dir, 'stormy') 55 | else: 56 | theme_path = os.path.join(themes_dir, 'sunny') 57 | background_image_path = os.path.join(theme_path, 'background.png') 58 | back_button_image_path = os.path.join(theme_path, 'back.png') 59 | return background_image_path, back_button_image_path 60 | 61 | def wrap_text(text, font, max_width): 62 | words = text.split(' ') 63 | lines = [] 64 | current_line = "" 65 | 66 | for word in words: 67 | test_line = current_line + word + " " 68 | if font.size(test_line)[0] <= max_width: 69 | current_line = test_line 70 | else: 71 | lines.append(current_line.strip()) 72 | current_line = word + " " 73 | 74 | lines.append(current_line.strip()) 75 | return lines 76 | 77 | def run(screen): 78 | pygame.font.init() # Initialize font module 79 | FONT_SIZE_SMALL = 30 80 | FONT_SIZE_MEDIUM = 50 81 | FONT_SIZE_LARGE = 350 82 | FONT_SIZE_DESCRIPTION = 35 83 | font_small = load_custom_font(font_path, FONT_SIZE_SMALL) # Load JetBrains Mono font 84 | font_medium = load_custom_font(font_path, FONT_SIZE_MEDIUM) 85 | font_large = load_custom_font(font_path_extra_bold, FONT_SIZE_LARGE) # Load JetBrains Mono Extra Bold font 86 | font_description = load_custom_font(font_path, FONT_SIZE_DESCRIPTION) 87 | WHITE = (255, 255, 255) 88 | BLUE = (50, 50, 100) # Dark blue for aesthetic background 89 | 90 | CENTER = (screen.get_width() // 2, screen.get_height() // 2) 91 | INFO_RADIUS = 350 # Radius to place text elements 92 | BACK_BUTTON_RADIUS = 60 # Radius for the back button 93 | BACK_BUTTON_POS = (CENTER[0], screen.get_height() - 100) # Position for the back button 94 | 95 | update_interval = 300 # 5 minutes in seconds 96 | next_update_time = time.time() 97 | 98 | weather_data = get_weather() # Fetch initial data 99 | 100 | def draw_text(screen, text, font, color, position): 101 | text_surface = font.render(text, True, color) 102 | text_rect = text_surface.get_rect(center=position) 103 | screen.blit(text_surface, text_rect) 104 | 105 | def draw_back_button(back_button_image): 106 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 107 | screen.blit(back_button_image, top_left) 108 | 109 | def check_click(pos): 110 | if math.hypot(pos[0] - BACK_BUTTON_POS[0], pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 111 | return False 112 | return True 113 | 114 | running = True 115 | while running: 116 | for event in pygame.event.get(): 117 | if event.type == pygame.QUIT: 118 | pygame.quit() 119 | sys.exit() 120 | elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: 121 | if not check_click(pygame.mouse.get_pos()): 122 | running = False 123 | 124 | current_time = time.time() 125 | if current_time >= next_update_time: 126 | weather_data = get_weather() 127 | query = "Give a brief description of what I should wear based on this weather data: " + str(weather_data) 128 | response = assist.ask_question_memory(query) 129 | print(response) 130 | next_update_time = current_time + update_interval 131 | 132 | screen.fill(BLUE) 133 | 134 | if weather_data and 'current' in weather_data: 135 | condition = weather_data['current']['condition']['text'].replace(" ", "").lower() 136 | background_image_path, back_button_image_path = load_theme(condition) 137 | 138 | if os.path.exists(background_image_path): 139 | background_image = pygame.image.load(background_image_path) 140 | background_image = pygame.transform.scale(background_image, (screen.get_width(), screen.get_height())) 141 | screen.blit(background_image, (0, 0)) 142 | else: 143 | print(f"No background image found for {condition}") 144 | 145 | if os.path.exists(back_button_image_path): 146 | back_button_image = pygame.image.load(back_button_image_path) 147 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 148 | draw_back_button(back_button_image) 149 | else: 150 | print(f"No back button image found for {condition}") 151 | 152 | if 'current' in weather_data: 153 | location_text = f"{weather_data['location']['name']}, {weather_data['location']['region']}" 154 | condition_text = weather_data['current']['condition']['text'] 155 | temp_text = f"{int(weather_data['current']['temp_f'])}°F" 156 | humidity_text = f"Humidity: {weather_data['current']['humidity']}%" 157 | wind_text = f"Wind: {int(weather_data['current']['wind_mph'])} mph" 158 | description_text = response 159 | 160 | # Move the temperature text down 161 | draw_text(screen, temp_text, font_large, WHITE, (CENTER[0], CENTER[1] - 50)) 162 | 163 | # Commented out the display of other weather details 164 | # draw_text(screen, condition_text, font_medium, WHITE, (CENTER[0], CENTER[1] - 50)) 165 | # draw_text(screen, location_text, font_medium, WHITE, (CENTER[0], CENTER[1] + 50)) 166 | # draw_text(screen, wind_text, font_small, WHITE, (CENTER[0], CENTER[1] + 100)) 167 | # draw_text(screen, humidity_text, font_small, WHITE, (CENTER[0], CENTER[1] + 130)) 168 | 169 | # Wrap description text with adjusted bounds 170 | description_lines = wrap_text(description_text, font_description, screen.get_width() - 200) 171 | line_height = font_description.get_linesize() 172 | for i, line in enumerate(description_lines): 173 | draw_text(screen, line, font_description, WHITE, (CENTER[0], CENTER[1] + 100 + i * line_height)) 174 | 175 | pygame.display.flip() 176 | 177 | 178 | 179 | #to add 180 | #partlycloudy 181 | #add sunny swap with partly cloudy 182 | #Check for rainy 183 | 184 | -------------------------------------------------------------------------------- /apps/app_4/app_4(old).py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | import random 4 | import sys 5 | import requests 6 | from io import BytesIO 7 | import time 8 | import math 9 | from dotenv import load_dotenv 10 | import spotipy 11 | from spotipy.oauth2 import SpotifyOAuth 12 | 13 | # Load environment variables from .env file 14 | load_dotenv() 15 | 16 | # Spotify configuration from .env 17 | username = os.getenv('SPOTIFY_USERNAME') 18 | clientID = os.getenv('SPOTIFY_CLIENT_ID') 19 | clientSecret = os.getenv('SPOTIFY_CLIENT_SECRET') 20 | redirect_uri = os.getenv('SPOTIFY_REDIRECT_URI') 21 | 22 | def get_current_playing_info(): 23 | global spotify 24 | 25 | current_track = spotify.current_user_playing_track() 26 | if current_track is None: 27 | return None # Return None if no track is playing 28 | 29 | # Extracting necessary details 30 | artist_name = current_track['item']['artists'][0]['name'] 31 | album_name = current_track['item']['album']['name'] 32 | album_cover_url = current_track['item']['album']['images'][0]['url'] 33 | track_title = current_track['item']['name'] # Get the track name 34 | 35 | return { 36 | "artist": artist_name, 37 | "album": album_name, 38 | "album_cover": album_cover_url, 39 | "title": track_title 40 | } 41 | 42 | def spotify_authenticate(client_id, client_secret, redirect_uri, username): 43 | # OAuth with the required scopes for playback control and reading currently playing track 44 | scope = "user-read-currently-playing user-modify-playback-state" 45 | auth_manager = SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope, username=username) 46 | return spotipy.Spotify(auth_manager=auth_manager) 47 | 48 | spotify = spotify_authenticate(clientID, clientSecret, redirect_uri, username) 49 | 50 | def start_music(): 51 | global spotify 52 | try: 53 | spotify.start_playback() 54 | except spotipy.SpotifyException as e: 55 | return f"Error in starting playback: {str(e)}" 56 | 57 | def stop_music(): 58 | global spotify 59 | try: 60 | spotify.pause_playback() 61 | except spotipy.SpotifyException as e: 62 | return f"Error in stopping playback: {str(e)}" 63 | 64 | def skip_to_next(): 65 | global spotify 66 | try: 67 | spotify.next_track() 68 | return "Skipped to next track." 69 | except spotipy.SpotifyException as e: 70 | return f"Error in skipping to next track: {str(e)}" 71 | 72 | def skip_to_previous(): 73 | global spotify 74 | try: 75 | spotify.previous_track() 76 | return "Skipped to previous track." 77 | except spotipy.SpotifyException as e: 78 | return f"Error in skipping to previous track: {str(e)}" 79 | 80 | def run(screen): 81 | pygame.init() 82 | running = True 83 | image_directory = './apps/app_4/records/' 84 | image_files = [os.path.join(image_directory, f) for f in os.listdir(image_directory) if os.path.isfile(os.path.join(image_directory, f))] 85 | random_image_path = random.choice(image_files) 86 | image = pygame.image.load(random_image_path) 87 | image = pygame.transform.scale(image, (1080, 1080)) 88 | banner_image_path = './resources/spotify/banner.png' 89 | banner_image = pygame.transform.scale(pygame.image.load(banner_image_path), (700, 250)) 90 | 91 | # Load control button images 92 | play_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/play.png'), (100, 100)) 93 | pause_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/pause.png'), (100, 100)) 94 | skip_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/skip.png'), (100, 100)) 95 | previous_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/previous.png'), (100, 100)) 96 | 97 | # Button positions 98 | back_button_center = (screen.get_width() // 2, screen.get_height() - 50) 99 | back_button_radius = 50 100 | control_buttons_y = back_button_center[1] - 170 # Position below the artist name 101 | play_pause_center = (screen.get_width() // 2 + 60, control_buttons_y) 102 | skip_button_center = (screen.get_width() // 2 + 190, control_buttons_y) 103 | previous_button_center = (screen.get_width() // 2 - 70, control_buttons_y) 104 | 105 | font = pygame.font.Font(None, 36) 106 | angle = 0 107 | angle_increment = -0.5 108 | last_check = 0 109 | spotify_details = None 110 | is_playing = True # Assume music is playing initially 111 | 112 | # Spinning variables 113 | center_of_image = (screen.get_width() // 2, screen.get_height() // 2) 114 | dragging = False 115 | last_mouse_pos = None 116 | 117 | while running: 118 | current_time = time.time() 119 | if current_time - last_check > 5: 120 | spotify_details = get_current_playing_info() 121 | last_check = current_time 122 | if spotify_details: 123 | response = requests.get(spotify_details['album_cover']) 124 | album_cover_image = pygame.transform.scale(pygame.image.load(BytesIO(response.content)), (200, 200)) 125 | 126 | for event in pygame.event.get(): 127 | if event.type == pygame.QUIT: 128 | pygame.quit() 129 | sys.exit() 130 | elif event.type == pygame.KEYDOWN: 131 | if event.key == pygame.K_ESCAPE: 132 | return 133 | elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: 134 | if math.hypot(event.pos[0] - back_button_center[0], event.pos[1] - back_button_center[1]) <= back_button_radius: 135 | return 136 | if math.hypot(event.pos[0] - play_pause_center[0]- 50, event.pos[1] - play_pause_center[1]- 50) <= 50: 137 | if is_playing: 138 | print("Pause") 139 | stop_music() 140 | is_playing = False 141 | angle_increment = 0 142 | else: 143 | print("Play") 144 | start_music() 145 | is_playing = True 146 | angle_increment = -0.5 147 | elif math.hypot(event.pos[0] - skip_button_center[0]- 50, event.pos[1] - skip_button_center[1]-50) <= 50: 148 | print("Skip") 149 | skip_to_next() 150 | elif math.hypot(event.pos[0] - previous_button_center[0]- 50, event.pos[1] - previous_button_center[1]- 50) <= 50: 151 | print("Previous") 152 | skip_to_previous() 153 | # Check if the click is within the image radius to start dragging 154 | elif math.hypot(event.pos[0] - center_of_image[0], event.pos[1] - center_of_image[1]) <= 540: 155 | dragging = True 156 | last_mouse_pos = event.pos 157 | elif event.type == pygame.MOUSEBUTTONUP and event.button == 1: 158 | dragging = False 159 | elif event.type == pygame.MOUSEMOTION and dragging: 160 | mouse_pos = pygame.mouse.get_pos() 161 | if last_mouse_pos: 162 | dx = mouse_pos[0] - last_mouse_pos[0] 163 | angle -= dx * 0.1 # Adjust rotation sensitivity as needed 164 | angle %= 360 # Keep the angle within 0-360 degrees 165 | last_mouse_pos = mouse_pos 166 | 167 | screen.fill((50, 50, 100)) 168 | rotated_image = pygame.transform.rotate(image, angle) 169 | rotated_rect = rotated_image.get_rect(center=center_of_image) 170 | screen.blit(rotated_image, rotated_rect) 171 | angle = (angle + angle_increment) % 360 172 | screen.blit(banner_image, (screen.get_width() // 2 - 350, back_button_center[1] - 300)) 173 | 174 | # Display music control buttons 175 | screen.blit(skip_button_image, skip_button_center) 176 | screen.blit(previous_button_image, previous_button_center) 177 | if is_playing: 178 | screen.blit(pause_button_image, play_pause_center) 179 | else: 180 | screen.blit(play_button_image, play_pause_center) 181 | 182 | if spotify_details: 183 | screen.blit(album_cover_image, (220, 750)) # Position for album cover 184 | song = f"{spotify_details['title']}" 185 | artist = f"{spotify_details['artist']}" 186 | song_surface = font.render(song, True, (255, 255, 255)) 187 | artist_surface = font.render(artist, True, (255, 255, 255)) 188 | song_x = (420 + 900 - song_surface.get_width()) // 2 189 | screen.blit(song_surface, (song_x, 750)) # Position for track info 190 | artist_x = (420 + 900 - artist_surface.get_width()) // 2 191 | screen.blit(artist_surface, (artist_x, 800)) 192 | 193 | pygame.draw.circle(screen, (255, 255, 255), back_button_center, back_button_radius, 2) 194 | text_surface = font.render('Back', True, (255, 255, 255)) 195 | text_rect = text_surface.get_rect(center=(back_button_center[0], back_button_center[1])) 196 | screen.blit(text_surface, text_rect) 197 | 198 | pygame.display.flip() 199 | -------------------------------------------------------------------------------- /apps/app_8/app_8.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import requests 3 | from bs4 import BeautifulSoup 4 | import os 5 | import time 6 | from io import BytesIO 7 | from dotenv import load_dotenv 8 | import math 9 | 10 | # Load environment variables from .env file 11 | load_dotenv() 12 | 13 | # Read sports from environment variables 14 | sports_to_scrape = os.getenv('SPORTS').split(',') 15 | 16 | # Initialize Pygame 17 | pygame.init() 18 | pygame.display.set_caption('Sports Scores App') 19 | clock = pygame.time.Clock() 20 | 21 | # Constants 22 | BLACK = (0, 0, 0) 23 | WHITE = (255, 255, 255) 24 | GREEN = (0, 255, 0) 25 | RED = (255, 0, 0) 26 | 27 | # Custom fonts 28 | font_path = './resources/fonts/good-times/good_times_rg.otf' 29 | font_small = pygame.font.Font(font_path, 30) 30 | font_large = pygame.font.Font(font_path, 60) 31 | font_sport = pygame.font.Font(font_path, 80) 32 | font_score = pygame.font.Font(font_path, 100) 33 | 34 | def get_scores(sports): 35 | url = 'https://www.foxsports.com/scores' 36 | response = requests.get(url) 37 | 38 | if response.status_code != 200: 39 | print(f"Failed to retrieve the webpage. Status code: {response.status_code}") 40 | return [] 41 | 42 | soup = BeautifulSoup(response.text, 'html.parser') 43 | scores_data = [] 44 | 45 | for sport in sports: 46 | div_id = f"sport_{sport}" 47 | sport_div = soup.find('div', id=div_id) 48 | 49 | if sport_div: 50 | games = sport_div.find_all('a', class_='score-chip') 51 | 52 | for game in games: 53 | teams = game.find_all('div', class_='score-team-row') 54 | 55 | if len(teams) == 2: 56 | team1_name = teams[0].find('span', class_='scores-text').text.strip() if teams[0].find('span', class_='scores-text') else "N/A" 57 | team2_name = teams[1].find('span', class_='scores-text').text.strip() if teams[1].find('span', class_='scores-text') else "N/A" 58 | 59 | team1_score_div = teams[0].find('div', class_='score-team-score') 60 | team2_score_div = teams[1].find('div', class_='score-team-score') 61 | 62 | team1_score = team1_score_div.find('span', class_='scores-text').text.strip() if team1_score_div and team1_score_div.find('span', class_='scores-text') else "N/A" 63 | team2_score = team2_score_div.find('span', class_='scores-text').text.strip() if team2_score_div and team2_score_div.find('span', class_='scores-text') else "N/A" 64 | 65 | team1_logo = teams[0].find('img', class_='team-logo')['src'].replace('80.80', '300.300') if teams[0].find('img', class_='team-logo') else None 66 | team2_logo = teams[1].find('img', class_='team-logo')['src'].replace('80.80', '300.300') if teams[1].find('img', class_='team-logo') else None 67 | 68 | scores_data.append({ 69 | 'sport': sport, 70 | 'team1_name': team1_name, 71 | 'team2_name': team2_name, 72 | 'team1_score': team1_score, 73 | 'team2_score': team2_score, 74 | 'team1_logo': team1_logo, 75 | 'team2_logo': team2_logo 76 | }) 77 | 78 | return scores_data 79 | 80 | def load_image_from_url(url): 81 | response = requests.get(url) 82 | image = pygame.image.load(BytesIO(response.content)) 83 | return image 84 | 85 | def run(screen): 86 | screen_width, screen_height = screen.get_size() 87 | CENTER = (screen_width // 2, screen_height // 2) 88 | BACK_BUTTON_POS = (screen_width // 2, screen_height * 9 // 10) 89 | BACK_BUTTON_RADIUS = 40 90 | rotate_interval = 10 # seconds 91 | fade_duration = 1 # seconds for fade-in effect 92 | fps = 30 # frames per second 93 | fade_speed = 255 / (fade_duration * fps) # Alpha change per frame for a 1-second fade 94 | 95 | background_image_path = './apps/app_2/background.jpg' 96 | background_image = pygame.image.load(background_image_path) 97 | background_image = pygame.transform.scale(background_image, (screen_width, screen_height)) 98 | 99 | back_button_image_path = './resources/back.png' 100 | back_button_image = pygame.image.load(back_button_image_path) 101 | back_button_image = pygame.transform.scale(back_button_image, (2 * BACK_BUTTON_RADIUS, 2 * BACK_BUTTON_RADIUS)) 102 | 103 | scores = get_scores(sports_to_scrape) 104 | score_index = 0 105 | alpha = 0 # Start from 0 for a fade-in effect 106 | fade_in = True 107 | start_time = time.time() 108 | 109 | def draw_back_button(): 110 | top_left = (BACK_BUTTON_POS[0] - BACK_BUTTON_RADIUS, BACK_BUTTON_POS[1] - BACK_BUTTON_RADIUS) 111 | screen.blit(back_button_image, top_left) 112 | 113 | running = True 114 | while running: 115 | screen.blit(background_image, (0, 0)) 116 | draw_back_button() 117 | 118 | current_time = time.time() 119 | 120 | # Check for automatic rotation based on time 121 | if current_time - start_time > rotate_interval: 122 | score_index = (score_index + 1) % len(scores) 123 | alpha = 0 # Reset alpha for fade-in effect 124 | fade_in = True 125 | start_time = current_time 126 | 127 | if scores: 128 | score = scores[score_index] 129 | try: 130 | team1_logo = load_image_from_url(score['team1_logo']) 131 | team2_logo = load_image_from_url(score['team2_logo']) 132 | team1_name = score['team1_name'] 133 | team2_name = score['team2_name'] 134 | team1_score = score['team1_score'] 135 | team2_score = score['team2_score'] 136 | sport_name = score['sport'] 137 | except Exception as e: 138 | print(f"Error loading score data: {e}") 139 | team1_logo = team2_logo = None 140 | team1_name = team2_name = "" 141 | team1_score = team2_score = "" 142 | sport_name = "" 143 | 144 | # Handle fade-in effect 145 | if fade_in: 146 | alpha += fade_speed 147 | if alpha >= 255: 148 | alpha = 255 149 | fade_in = False 150 | 151 | if team1_logo and team2_logo: 152 | team1_logo = pygame.transform.scale(team1_logo, (300, 300)) 153 | team2_logo = pygame.transform.scale(team2_logo, (300, 300)) 154 | team1_logo.set_alpha(int(alpha)) 155 | team2_logo.set_alpha(int(alpha)) 156 | 157 | screen.blit(team1_logo, (CENTER[0] - 400, CENTER[1] - 100)) 158 | screen.blit(team2_logo, (CENTER[0] + 100, CENTER[1] - 100)) 159 | 160 | sport_text_surface = font_sport.render(sport_name, True, WHITE) 161 | sport_text_surface.set_alpha(int(alpha)) 162 | sport_text_rect = sport_text_surface.get_rect(center=(CENTER[0], CENTER[1] - 400)) 163 | screen.blit(sport_text_surface, sport_text_rect) 164 | 165 | team1_text_surface = font_large.render(team1_name, True, WHITE) 166 | team1_text_surface.set_alpha(int(alpha)) 167 | team1_text_rect = team1_text_surface.get_rect(center=(CENTER[0] - 250, CENTER[1] - 250)) 168 | screen.blit(team1_text_surface, team1_text_rect) 169 | 170 | team2_text_surface = font_large.render(team2_name, True, WHITE) 171 | team2_text_surface.set_alpha(int(alpha)) 172 | team2_text_rect = team2_text_surface.get_rect(center=(CENTER[0] + 250, CENTER[1] - 250)) 173 | screen.blit(team2_text_surface, team2_text_rect) 174 | 175 | score_text = f"{team1_score} - {team2_score}" 176 | score_surface = font_score.render(score_text, True, WHITE) 177 | score_surface.set_alpha(int(alpha)) 178 | score_rect = score_surface.get_rect(center=(CENTER[0], CENTER[1] + 300)) 179 | screen.blit(score_surface, score_rect) 180 | else: 181 | print("No scores to display.") 182 | 183 | for event in pygame.event.get(): 184 | if event.type == pygame.QUIT: 185 | pygame.quit() 186 | sys.exit() 187 | elif event.type == pygame.MOUSEBUTTONDOWN: 188 | # Check if back button is clicked 189 | if math.hypot(event.pos[0] - BACK_BUTTON_POS[0], event.pos[1] - BACK_BUTTON_POS[1]) <= BACK_BUTTON_RADIUS: 190 | running = False # Exit the loop if back button is pressed 191 | else: 192 | # Go to the next game on any other click 193 | score_index = (score_index + 1) % len(scores) 194 | alpha = 0 # Reset alpha for new fade-in effect 195 | fade_in = True 196 | start_time = current_time 197 | 198 | pygame.display.flip() 199 | clock.tick(fps) 200 | 201 | if __name__ == "__main__": 202 | pygame.init() 203 | screen = pygame.display.set_mode((1080, 1080)) 204 | pygame.display.set_caption("Sports Scores App") 205 | run(screen) 206 | pygame.quit() 207 | -------------------------------------------------------------------------------- /apps/app_4/app_4.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | import sys 4 | import requests 5 | from io import BytesIO 6 | import time 7 | import math 8 | from dotenv import load_dotenv 9 | import spotipy 10 | from spotipy.oauth2 import SpotifyOAuth 11 | 12 | # Load environment variables from .env file 13 | load_dotenv() 14 | 15 | # Spotify configuration from .env 16 | username = os.getenv('SPOTIFY_USERNAME') 17 | clientID = os.getenv('SPOTIFY_CLIENT_ID') 18 | clientSecret = os.getenv('SPOTIFY_CLIENT_SECRET') 19 | redirect_uri = os.getenv('SPOTIFY_REDIRECT_URI') 20 | 21 | def get_current_playing_info(): 22 | global spotify 23 | 24 | current_track = spotify.current_user_playing_track() 25 | if current_track is None: 26 | return None # Return None if no track is playing 27 | 28 | # Extracting necessary details 29 | artist_name = current_track['item']['artists'][0]['name'] 30 | album_name = current_track['item']['album']['name'] 31 | album_cover_url = current_track['item']['album']['images'][0]['url'] 32 | track_title = current_track['item']['name'] # Get the track name 33 | 34 | return { 35 | "artist": artist_name, 36 | "album": album_name, 37 | "album_cover": album_cover_url, 38 | "title": track_title 39 | } 40 | 41 | def spotify_authenticate(client_id, client_secret, redirect_uri, username): 42 | # OAuth with the required scopes for playback control and reading currently playing track 43 | scope = "user-read-currently-playing user-modify-playback-state" 44 | auth_manager = SpotifyOAuth(client_id, client_secret, redirect_uri, scope=scope, username=username) 45 | return spotipy.Spotify(auth_manager=auth_manager) 46 | 47 | spotify = spotify_authenticate(clientID, clientSecret, redirect_uri, username) 48 | 49 | def start_music(): 50 | global spotify 51 | try: 52 | spotify.start_playback() 53 | except spotipy.SpotifyException as e: 54 | return f"Error in starting playback: {str(e)}" 55 | 56 | def stop_music(): 57 | global spotify 58 | try: 59 | spotify.pause_playback() 60 | except spotipy.SpotifyException as e: 61 | return f"Error in stopping playback: {str(e)}" 62 | 63 | def skip_to_next(): 64 | global spotify 65 | try: 66 | spotify.next_track() 67 | return "Skipped to next track." 68 | except spotipy.SpotifyException as e: 69 | return f"Error in skipping to next track: {str(e)}" 70 | 71 | def skip_to_previous(): 72 | global spotify 73 | try: 74 | spotify.previous_track() 75 | return "Skipped to previous track." 76 | except spotipy.SpotifyException as e: 77 | return f"Error in skipping to previous track: {str(e)}" 78 | 79 | def scale_text_to_fit(surface, text, font_path, max_width, initial_size): 80 | font_size = initial_size 81 | font = pygame.font.Font(font_path, font_size) 82 | while font.size(text)[0] > max_width and font_size > 10: # Ensure the font size does not go below 10 83 | font_size -= 1 84 | font = pygame.font.Font(font_path, font_size) 85 | return font 86 | 87 | def run(screen): 88 | pygame.init() 89 | running = True 90 | 91 | banner_image_path = './apps/app_4/banner.png' 92 | banner_image = pygame.transform.scale(pygame.image.load(banner_image_path), (700, 250)) 93 | 94 | menu_image_path = './resources/spotify/menu.png' 95 | menu_image = pygame.transform.scale(pygame.image.load(menu_image_path), (700, 250)) 96 | 97 | # Load control button images 98 | play_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/play.png'), (100, 100)) 99 | pause_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/pause.png'), (100, 100)) 100 | skip_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/skip.png'), (100, 100)) 101 | previous_button_image = pygame.transform.scale(pygame.image.load('./resources/spotify/previous.png'), (100, 100)) 102 | 103 | # Load back button image 104 | back_button_image_path = './apps/app_4/back.png' 105 | back_button_image = pygame.transform.scale(pygame.image.load(back_button_image_path), (100, 100)) 106 | 107 | # Button positions 108 | back_button_center = (screen.get_width() // 2, screen.get_height() - 50) 109 | control_buttons_y = back_button_center[1] - 170 # Position below the artist name 110 | play_pause_center = (screen.get_width() // 2, control_buttons_y) 111 | skip_button_center = (screen.get_width() // 2 + 150, control_buttons_y) 112 | previous_button_center = (screen.get_width() // 2 - 150, control_buttons_y) 113 | 114 | # Load custom fonts 115 | song_font_path = './apps/app_4/JetBrainsMono-ExtraBold.ttf' 116 | artist_font_path = './apps/app_4/JetBrainsMono.ttf' 117 | initial_song_font_size = 48 # Initial font size for song title 118 | artist_font_size = 36 119 | 120 | last_check = 0 121 | spotify_details = None 122 | is_playing = True # Assume music is playing initially 123 | 124 | while running: 125 | current_time = time.time() 126 | if current_time - last_check > 5: 127 | spotify_details = get_current_playing_info() 128 | last_check = current_time 129 | if spotify_details: 130 | response = requests.get(spotify_details['album_cover']) 131 | album_cover_image = pygame.transform.scale(pygame.image.load(BytesIO(response.content)), (screen.get_width(), screen.get_height())) 132 | 133 | for event in pygame.event.get(): 134 | if event.type == pygame.QUIT: 135 | pygame.quit() 136 | sys.exit() 137 | elif event.type == pygame.KEYDOWN: 138 | if event.key == pygame.K_ESCAPE: 139 | return 140 | elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: 141 | if math.hypot(event.pos[0] - back_button_center[0], event.pos[1] - back_button_center[1]) <= 50: 142 | return 143 | if math.hypot(event.pos[0] - play_pause_center[0], event.pos[1] - play_pause_center[1]) <= 50: 144 | if is_playing: 145 | print("Pause") 146 | stop_music() 147 | is_playing = False 148 | else: 149 | print("Play") 150 | start_music() 151 | is_playing = True 152 | elif math.hypot(event.pos[0] - skip_button_center[0], event.pos[1] - skip_button_center[1]) <= 50: 153 | print("Skip") 154 | skip_to_next() 155 | elif math.hypot(event.pos[0] - previous_button_center[0], event.pos[1] - previous_button_center[1]) <= 50: 156 | print("Previous") 157 | skip_to_previous() 158 | 159 | if spotify_details: 160 | screen.blit(album_cover_image, (0, 0)) # Set album cover as background 161 | screen.blit(banner_image, (screen.get_width() // 2 - 350, back_button_center[1] - 350)) 162 | screen.blit(menu_image, (screen.get_width() // 2 - 350, back_button_center[1] - 350)) 163 | 164 | # Display music control buttons 165 | screen.blit(previous_button_image, (previous_button_center[0] - previous_button_image.get_width() // 2, previous_button_center[1] - previous_button_image.get_height() // 2)) 166 | if is_playing: 167 | screen.blit(pause_button_image, (play_pause_center[0] - pause_button_image.get_width() // 2, play_pause_center[1] - pause_button_image.get_height() // 2)) 168 | else: 169 | screen.blit(play_button_image, (play_pause_center[0] - play_button_image.get_width() // 2, play_pause_center[1] - play_button_image.get_height() // 2)) 170 | screen.blit(skip_button_image, (skip_button_center[0] - skip_button_image.get_width() // 2, skip_button_center[1] - skip_button_image.get_height() // 2)) 171 | 172 | song = f"{spotify_details['title']}" 173 | artist = f"{spotify_details['artist']}" 174 | 175 | # Scale the song title text to fit within the banner 176 | max_width = 700 - 20 # Banner width minus some padding 177 | song_font = scale_text_to_fit(screen, song, song_font_path, max_width, initial_song_font_size) 178 | song_surface = song_font.render(song, True, (255, 255, 255)) 179 | artist_font = pygame.font.Font(artist_font_path, artist_font_size) 180 | artist_surface = artist_font.render(artist, True, (255, 255, 255)) 181 | 182 | # Calculate positions to place the text inside the banner 183 | banner_rect = banner_image.get_rect(center=(screen.get_width() // 2, back_button_center[1] - 325)) 184 | total_text_height = song_surface.get_height() + artist_surface.get_height() + 10 # 10 pixels for spacing 185 | song_y = banner_rect.top + (banner_rect.height - total_text_height) // 2 + 40 186 | artist_y = song_y + song_surface.get_height() + 10 187 | 188 | song_x = banner_rect.left + (banner_rect.width - song_surface.get_width()) // 2 189 | artist_x = banner_rect.left + (banner_rect.width - artist_surface.get_width()) // 2 190 | 191 | screen.blit(song_surface, (song_x, song_y)) 192 | screen.blit(artist_surface, (artist_x, artist_y)) 193 | 194 | # Display the back button 195 | screen.blit(back_button_image, (back_button_center[0] - back_button_image.get_width() // 2, back_button_center[1] - back_button_image.get_height() // 2)) 196 | 197 | pygame.display.flip() 198 | -------------------------------------------------------------------------------- /home_screen.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import sys 4 | import os 5 | import assist 6 | import time 7 | import tools 8 | from RealtimeSTT import AudioToTextRecorder 9 | import threading 10 | 11 | class AppCircle: 12 | def __init__(self, center, app_index, screen_size): 13 | self.center = center 14 | self.radius = min(screen_size) // 10 15 | self.app_index = app_index 16 | self.image = self.load_image(app_index) 17 | self.text = f'App {app_index}' 18 | 19 | def load_image(self, app_index): 20 | path = f'./apps/app_{app_index}/app_{app_index}.png' 21 | try: 22 | img = pygame.image.load(path) 23 | img = pygame.transform.scale(img, (2 * self.radius, 2 * self.radius)) 24 | return img 25 | except FileNotFoundError: 26 | print(f"Image file not found: {path}") 27 | return None 28 | 29 | def draw(self, screen): 30 | if self.image: 31 | top_left = (self.center[0] - self.radius, self.center[1] - self.radius) 32 | screen.blit(self.image, top_left) 33 | else: 34 | pygame.draw.circle(screen, (255, 255, 255), self.center, self.radius) 35 | font = pygame.font.Font(None, 32) 36 | text_surface = font.render(self.text, True, (0, 0, 0)) 37 | text_rect = text_surface.get_rect(center=self.center) 38 | screen.blit(text_surface, text_rect) 39 | 40 | def is_clicked(self, pos): 41 | return math.hypot(pos[0] - self.center[0], pos[1] - self.center[1]) <= self.radius 42 | 43 | def create_circles(screen_size): 44 | circles = [] 45 | num_circles = 8 46 | angle_step = 360 / num_circles 47 | center_x, center_y = screen_size[0] // 2, screen_size[1] // 2 48 | radius = min(screen_size) // 2.6 49 | 50 | for i in range(num_circles): 51 | angle = math.radians(angle_step * i) 52 | x = int(center_x + radius * math.cos(angle)) 53 | y = int(center_y + radius * math.sin(angle)) 54 | circles.append(AppCircle((x, y), i + 1, screen_size)) 55 | return circles 56 | 57 | 58 | def create_text_surfaces(response, font, screen_width, margin): 59 | words = response.split() 60 | lines = [] 61 | current_line = [] 62 | for word in words: 63 | test_line = current_line + [word] 64 | test_surface = font.render(' '.join(test_line), True, (255, 255, 255)) 65 | if test_surface.get_width() <= screen_width - 2 * margin: 66 | current_line = test_line 67 | else: 68 | lines.append(' '.join(current_line)) 69 | current_line = [word] 70 | lines.append(' '.join(current_line)) 71 | 72 | text_surfaces = [font.render(line, True, (255, 255, 255)) for line in lines] 73 | total_height = sum(surface.get_height() for surface in text_surfaces) 74 | start_y = (screen.get_height() - total_height) // 2 75 | 76 | text_rects = [surface.get_rect(center=(screen_width // 2, start_y + i * surface.get_height())) for i, surface in enumerate(text_surfaces)] 77 | 78 | return text_surfaces, text_rects 79 | 80 | def apply_blur_ring_and_text(screen, text, blue_ring_thickness=100): 81 | """Apply a simplified blur effect to the screen, draw a subtle transparent blue ring, and overlay text.""" 82 | 83 | # Create a semi-transparent surface for the blur effect 84 | blur_surface = pygame.Surface(screen.get_size(), pygame.SRCALPHA) 85 | blur_surface.fill((0, 0, 0, 128)) # Fill with semi-transparent black (adjust alpha for blur intensity) 86 | 87 | # Draw the blurred screen back onto the main screen 88 | screen.blit(blur_surface, (0, 0)) 89 | 90 | # Create a transparent surface for the gradient ring effect 91 | ring_surface = pygame.Surface(screen.get_size(), pygame.SRCALPHA) 92 | 93 | # Define the gradient effect 94 | center = (screen.get_width() // 2, screen.get_height() // 2) 95 | outer_radius = screen.get_width() // 2 96 | inner_radius = outer_radius - blue_ring_thickness 97 | 98 | # Draw a gradient ring from outer to inner radius 99 | for i in range(outer_radius, inner_radius, -1): 100 | alpha = int(128 * (i - inner_radius) / blue_ring_thickness) 101 | pygame.draw.circle(ring_surface, (173, 216, 230, alpha), center, i) 102 | 103 | # Overlay the ring surface onto the screen 104 | screen.blit(ring_surface, (0, 0)) 105 | 106 | # Create text surfaces 107 | font = pygame.font.Font(None, 36) 108 | margin = screen.get_width() // 4 109 | text_surfaces, text_rects = create_text_surfaces(text, font, screen.get_width(), margin) 110 | 111 | # Draw the text surfaces on top of the blurred background and ring 112 | for text_surface, text_rect in zip(text_surfaces, text_rects): 113 | screen.blit(text_surface, text_rect) 114 | 115 | # Update the display to show changes 116 | pygame.display.update() 117 | 118 | def run_voice_assistant(circles, screen, background, draw_event, idle_event): 119 | recorder = AudioToTextRecorder(spinner=False, model="tiny.en", language="en", post_speech_silence_duration=0.1, silero_sensitivity=0.4) 120 | query_displayed = False 121 | response_displayed = False 122 | query_surfaces = [] 123 | query_rects = [] 124 | response_surfaces = [] 125 | response_rects = [] 126 | hot_words = ["jarvis", "alexa"] 127 | skip_hot_word_check = False 128 | print("Say something...") 129 | 130 | while True: 131 | current_text = recorder.text() 132 | if any(hot_word in current_text.lower() for hot_word in hot_words) or skip_hot_word_check: 133 | if current_text: 134 | print("User: " + current_text) 135 | 136 | # Indicate that voice assistant is drawing 137 | draw_event.set() 138 | idle_event.clear() 139 | 140 | # Start the blur effect and display the query 141 | apply_blur_ring_and_text(screen, current_text, blue_ring_thickness=100) 142 | # query_surfaces, query_rects = display_query(screen, current_text, circles, background) 143 | query_displayed = True 144 | 145 | recorder.stop() 146 | current_text = current_text + " " + time.strftime("%Y-%m-%d %H-%M-%S") 147 | response = assist.ask_question_memory(current_text) 148 | print(response) 149 | speech = response.split('#')[0] 150 | 151 | if query_displayed: 152 | screen.blit(background, (0, 0)) # Draw background first 153 | for circle in circles: # Then draw apps (circles) 154 | circle.draw(screen) 155 | # Immediately fade out the query much faster 156 | apply_blur_ring_and_text(screen, response, blue_ring_thickness=100) 157 | query_displayed = False 158 | response_displayed = True 159 | 160 | # if response_displayed: 161 | # apply_blur_ring_and_text(screen, response, blue_ring_thickness=100) 162 | 163 | done = assist.TTS(speech) 164 | if len(response.split('#')) > 1: 165 | command = response.split('#')[1] 166 | tools.parse_command(command) 167 | recorder.start() 168 | 169 | # After the response has been displayed for some time, fade everything out 170 | if response_displayed: 171 | pygame.time.delay(3000) # Shorter delay to speed up transition 172 | # Fade out the response and blur effect 173 | response_displayed = False 174 | 175 | # Indicate that the voice assistant is done drawing 176 | idle_event.set() 177 | draw_event.clear() 178 | 179 | # Draw the normal screen when there is no query or response to show 180 | if not query_displayed and not response_displayed and idle_event.is_set(): 181 | screen.blit(background, (0, 0)) # Draw background first 182 | for circle in circles: # Then draw apps (circles) 183 | circle.draw(screen) 184 | 185 | def run_home_screen(screen): 186 | screen_size = screen.get_size() 187 | background = pygame.image.load('./resources/background.jpg') 188 | background = pygame.transform.scale(background, screen_size) 189 | 190 | circles = create_circles(screen_size) 191 | 192 | running = True 193 | draw_event = threading.Event() 194 | idle_event = threading.Event() 195 | idle_event.set() # Initially set the idle event to allow drawing 196 | 197 | print("Here") 198 | voice_thread = threading.Thread(target=run_voice_assistant, args=(circles, screen, background, draw_event, idle_event)) 199 | voice_thread.daemon = True 200 | voice_thread.start() 201 | print("Here2") 202 | 203 | while running: 204 | for event in pygame.event.get(): 205 | if event.type == pygame.QUIT: 206 | pygame.quit() 207 | sys.exit() 208 | elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: 209 | for circle in circles: 210 | if circle.is_clicked(event.pos): 211 | app_index = circles.index(circle) + 1 212 | app_module_name = f'apps.app_{app_index}.app_{app_index}' 213 | mod = __import__(app_module_name, fromlist=['']) 214 | mod.run(screen) 215 | 216 | # Draw home screen only if the voice assistant is idle 217 | if idle_event.is_set(): 218 | screen.blit(background, (0, 0)) # Draw background first 219 | for circle in circles: # Then draw apps (circles) 220 | circle.draw(screen) 221 | 222 | pygame.display.flip() 223 | pygame.time.delay(1) 224 | 225 | 226 | 227 | 228 | 229 | 230 | if __name__ == '__main__': 231 | pygame.init() 232 | screen = pygame.display.set_mode((1080, 1080)) 233 | 234 | # Run the home screen 235 | run_home_screen(screen,) 236 | --------------------------------------------------------------------------------