Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
DreamingElectricSheep committed Apr 1, 2024
1 parent 77265fc commit 6174f69
Show file tree
Hide file tree
Showing 23 changed files with 2,943 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
27 changes: 27 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help improve this project
title: ''
labels: 'bug'
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots and videos**
If possible, add screenshots or videos to help explain your problem.

**Additional context**
Add any other context about the problem here.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

*.pyc
*.bsdesign
*.blend1
91 changes: 91 additions & 0 deletions Development/time_sleep_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from datetime import datetime
import time

# Returns the current time (timezone sensitive) in the format [hour, minute, second]
# where each value is an integer
def getTime():
time = str(datetime.now()).split(" ")[1].split(".")[0]
time_array = time.split(":")

for i in range(len(time_array)):
time_array[i] = int(time_array[i])

return time_array

# Formats the getTime output into "hh:mm:ss"
def formatTime(time_array, hour = False, minute = False, second = False, ampm = False):
# Don't alter original data
time_array = time_array.copy()

time_value = ""
# Remove any values that user doesn't want
if second == False:
time_array.pop(2)
if minute == False:
time_array.pop(1)
if hour == False:
time_array.pop(0)
elif ampm == True: # If user wants it in 24-hour time
if time_array[0] >= 12:
time_array[0] -= 12
time_value = "PM"
else:
time_value = "AM"

if time_array[0] == 0:
time_array[0] = 12

for i in range(len(time_array)):
time_array[i] = str(time_array[i])

time = f"{':'.join(time_array)} {time_value}"

return time

# Returns a string of today's day
def getDay():
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
day = datetime.weekday(datetime.now())
return days[day], day

on_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
on_time_range = [[8, 0, 0], [16, 30, 0]]


# Startup information
print(f"\n{formatTime(getTime(), hour=True, minute=True, second=True, ampm=True)}")
print(getDay()[0])

# Main loop
while True:
# Get today's day
this_day = getDay()
run_check = False

# While it's a weekday
while this_day[0] in on_days:
# Get current time
this_time = getTime()
print(f"\nCurrent time: {formatTime(this_time, hour=True, minute=True, second=True, ampm=True)}")

# Checks if current time and day is within valid range
run_check = False
if (this_day[0] in on_days) and ((this_time[0] > on_time_range[0][0] and this_time[0] < on_time_range[1][0]) or ((this_time[0] == on_time_range[0][0]) and ((this_time[1] > on_time_range[0][1]) or ((this_time[1] == on_time_range[0][1]) and (this_time[2] >= on_time_range[0][2])))) or ((this_time[0] == on_time_range[1][0]) and ((this_time[1] < on_time_range[1][1]) or ((this_time[1] == on_time_range[1][1]) and (this_time[2] < on_time_range[1][2]))))):
run_check = True

# If it's supposed to be displaying
if run_check == True:
# Display anything in here
pass
else:
time.sleep(60)

# While it's a weekend
while this_day[0] not in on_days:
this_day = getDay()
print(f"\nStopped for 24 hours because today is {this_day[0]}.\nWait until one of these days: {on_days}.")

# Don't do anything for 7h59m59s
# I chose that specific number so that there's enough time for it to
# recheck the day and go into normal operation by 8am.
time.sleep(28799)
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Sooraj Sannabhadti

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# LED Light Wall Project

## Contents

1. [Contents](#contents)
2. [About](#about)
3. [Installation](#installation)
4. [Usage](#usage)\
a. [Physical Version](#physical-version)\
b. [Testing Version](#testing-version)
5. [Features](#features)

## About

This is a passion project, made independently, and exclusively by two high school students: [WhenLifeHandsYouLemons](https://github.com/WhenLifeHandsYouLemons) and [DreamingElectricSheep](https://github.com/DreamingElectricSheep).

This project is our creation of an LED light wall-- Essentially a large screen that is able to display pretty much anything-- just like the screens on your phone, computer or TV. We've specialized ours to act as a sort of visual spectacle, inpired by the likes of Fireworks, Lazershows, and Nanoleafs. Our LED light wall displays a continous flow of random [patterns](#features)-- largely inspired by natural patterns like waves, which can also be controlled via ultrasonic sensors. This GitHub repository contains all of the code and documentation throughout the development of our project.

This enourmous project took more than a year to complete, covering far more than just the scope of Computer Science and Programming-- Enginnering, Design, Electronics, Modeling, Planning and of course, Programming and Mathematics were all integral processes in the creation of this project.

The documentation is hosted at: <https://whenlifehandsyoulemons.github.io/LED-Light-Wall/>.

This project was initially in a private repository and on May 24, 2023, it was moved to a new public repository and so much of the git history isn't present here.

## Installation

To install this project, you will need to clone the repository and then install the dependencies. To do this, run the following commands on a Raspberry Pi (RPi 3 or newer with Raspberry Pi OS installed):

```console
git clone https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall.git
cd LED-Light-Wall
pip install requirements.txt
```

## Usage

### Physical Version

To run this project with a physical board, you will need to run the `rpi_main.py` file. To do this, run the following command:

```console
cd LED-Light-Wall
sudo python3 rpi_main.py
```

**Note**: If you're using the [Thonny IDE](https://thonny.org/), you need to run the `rpi_main.py` file in sudo mode. To do so, run the `open_sudo_thonny.py` file first (located inside the "*RPi Files*" folder), then open the `rpi_main.py` file inside Thonny and run it.

### Testing Version

To run the testing version, you will need to run the `pygame_main.py` file (located inside the "*Testing Version*" folder). To do this, run the following command:

```console
cd LED-Light-Wall/Testing\ Version
python pygame_main.py
```

**Note**: The testing version lacks full parity with the physical version. It is only used to test patterns that can be precomputed. Images, text, ultrasonic sensors, and graphics are not supported in the testing version. The testing version doesn't include the same number of erroneous data checks as the physical version so unseen errors may occur when porting to the physical version.

## Features

- Displays a multitude of extensively customizable patterns and graphics, both continious and static:
- [Circular waves](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/precomputations.py#L83)
- [Square waves](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/precomputations.py#L18)
- [Normal waves](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/precomputations.py#L162)
- [Rain drops](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/precomputations.py#L197)
- [Lines](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/precomputations.py#L210)
- [Circles](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/graphics.py#L40)
- [Rectangles](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/graphics.py#L31)
- [Scrolling text](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/graphics.py#L51)
- [Images](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/sblob/main/images.py)
- [Ultrasonic sensors](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/ultrasonics.py)
- Uses ultrasonic sensors to detect when an object is near the board and starts a different pattern.
- [Power saving mode](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/blob/main/rpi_main.py#L111)
- Turns off the display on certain days and at certain times to conserve power.
- [Includes a testing version](https://github.com/WhenLifeHandsYouLemons/LED-Light-Wall/tree/main/Testing%20Version)
- Can be used to test patterns without the need for a physical board.

[Back to top](#led-light-wall-project)
2 changes: 2 additions & 0 deletions RPi Files/open_sudo_thonny.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import os
os.system("sudo thonny")
14 changes: 14 additions & 0 deletions Testing Version/pygame_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Constants
"""
# Variables for Pygame
BOARD_WIDTH = 30
BOARD_HEIGHT = 20
PIXEL_SIZE = 20
GAP_SIZE = 5

# Pygame window
BACKGROUND_COLOUR = 7, 7, 7
WINDOW_HEIGHT = (PIXEL_SIZE + GAP_SIZE) * BOARD_HEIGHT
WINDOW_WIDTH = (PIXEL_SIZE + GAP_SIZE) * BOARD_WIDTH
RUNNING_WINDOW = True
132 changes: 132 additions & 0 deletions Testing Version/pygame_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""
Main loop
"""
import sys
import pygame
from datetime import datetime
import time

# Custom imports
from pygame_constants import *
from pygame_utilities import *
from pygame_precomputations import *


# Returns the current time (timezone sensitive) in the format [hour, minute, second]
# where each value is an integer
def getTime():
time = str(datetime.now()).split(" ")[1].split(".")[0]
time_array = time.split(":")

for i in range(len(time_array)):
time_array[i] = int(time_array[i])

return time_array

# Formats the getTime output into "hh:mm:ss"
def formatTime(time_array, hour = False, minute = False, second = False, ampm = False):
# Don't alter original data
time_array = time_array.copy()

time_value = ""
# Remove any values that user doesn't want
if second == False:
time_array.pop(2)
if minute == False:
time_array.pop(1)
if hour == False:
time_array.pop(0)
elif ampm == True: # If user wants it in 24-hour time
if time_array[0] >= 12:
time_array[0] -= 12
time_value = "PM"
else:
time_value = "AM"

if time_array[0] == 0:
time_array[0] = 12

for i in range(len(time_array)):
time_array[i] = str(time_array[i])

time = f"{':'.join(time_array)} {time_value}"

return time

# Returns a string of today's day
def getDay():
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
day = datetime.weekday(datetime.now())
return days[day], day

# Allowed time and days
on_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Uses 24-hour time format
on_time_range = [[8, 0, 0], [16, 30, 0]]

# Reset board
setAllPixelsColour(COLOURS["Black"])

# Startup information
print("\nStarting display.")
print(f"\n{formatTime(getTime(), hour=True, minute=True, second=True, ampm=True)}")
print(getDay()[0])

# Main loop
while RUNNING_WINDOW:
clock.tick(60)

# Get today's day
this_day = getDay()
run_check = False

# While it's a weekday
while this_day[0] in on_days and RUNNING_WINDOW:
# Get current time
this_time = getTime()
print(f"\nCurrent time: {formatTime(this_time, hour=True, minute=True, second=True, ampm=True)}")

# Checks if current time and day is within valid range
run_check = False
# I've shortened it to hopefully make it check faster
if (this_day[0] in on_days) and ((this_time[0] > on_time_range[0][0] and this_time[0] < on_time_range[1][0]) or ((this_time[0] == on_time_range[0][0]) and ((this_time[1] > on_time_range[0][1]) or ((this_time[1] == on_time_range[0][1]) and (this_time[2] >= on_time_range[0][2])))) or ((this_time[0] == on_time_range[1][0]) and ((this_time[1] < on_time_range[1][1]) or ((this_time[1] == on_time_range[1][1]) and (this_time[2] < on_time_range[1][2]))))):
run_check = True

# If it's supposed to be displaying
if run_check == True:
# Do stuff
print("\nCalculating waves...")
merged, non_merged = randomisePatterns()

print("Displaying waves.")
displayRandomPatterns(merged, non_merged, 0.05)

setAllPixelsColour(COLOURS["Black"])

pygame.display.update()
else:
# Wait for 5 minutes before rechecking the day and time
time.sleep(60 * 5)

pygame.display.update()

for event in pygame.event.get():
if event.type == pygame.QUIT:
RUNNING_WINDOW = False
pygame.quit()

# While it's a weekend
while this_day[0] not in on_days and RUNNING_WINDOW:
print(f"\nStopped for 8 hours because today is {this_day[0]}.\nWait until one of these days: {on_days}.")

# Don't do anything for the minimum amount of time possible - 10 seconds
# I choose the number so that there's enough time for it to
# recheck the day and go into normal operation by it's specified on time.
time.sleep((on_time_range[0][0] * 60 * 60) + (on_time_range[0][1] * 60) + (on_time_range[0][2]) - 10)

# Recheck the day after sleeping
# This fixes an issue where it would get the day but finish the loop
# once more before exiting, which meant it sleeps for 8 hours on Monday
this_day = getDay()

sys.exit()
Loading

0 comments on commit 6174f69

Please sign in to comment.