diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..071b335 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +log.txt +SECRET \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..71e4f7c --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/README.md b/README.md index b34171c..5dd9183 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ + +I have used code from this fork but added code on top of it with authentication and a garage stream + +- Added only one sensor for open not close(This was a personal thing, I didnt have long enough wire to add another sensor). +- Adding a camera using IPWebCam and an old phone +- Changed UI to reflect some new changes including a garage live stream and a trigger button +- Added flask login + +_________________________________________________________________________ YouTube Video Instructions found here: https://youtu.be/Fcx6wANw9KM Setting up a Flask web server to control your garage door & display the door status & log usage. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/camera.py b/camera.py new file mode 100644 index 0000000..4432ef7 --- /dev/null +++ b/camera.py @@ -0,0 +1,17 @@ +# this is just an example for how to use an IP camera + +import cv2 +import time +import numpy as np + +capture = cv2.VideoCapture('http://192.168.68.118:4747/video') + +while(True): + ret, frame = capture.read() + cv2.imshow('frame',frame) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + time.sleep(5) + +capture.release() +cv2.destroyAllWindows() \ No newline at end of file diff --git a/data.py b/data.py new file mode 100644 index 0000000..9227423 --- /dev/null +++ b/data.py @@ -0,0 +1,18 @@ +class User(): + def __init__(self, id, username, active=True): + self.id = id + self.username = username + self.active = active + + def is_active(self): + return True + + def is_authenticated(self): + return True + + def get_id(self): + return self.id + +USERS = { + "1221" : User("1221", 'user1') +} \ No newline at end of file diff --git a/garageFunctions.py b/garageFunctions.py new file mode 100644 index 0000000..4e8d6ae --- /dev/null +++ b/garageFunctions.py @@ -0,0 +1,60 @@ +import RPi.GPIO as GPIO +import time +from werkzeug.security import generate_password_hash, check_password_hash +import cv2 +import numpy as np + +import RPi.GPIO as GPIO +# the pin numbers refer to the board connector not the chip +GPIO.setmode(GPIO.BOARD) +GPIO.setwarnings(False) +# set up pin ?? (one of the above listed pins) as an input with a pull-up resistor +GPIO.setup(18, GPIO.IN, GPIO.PUD_UP) +GPIO.setup(7, GPIO.OUT) +GPIO.output(7, GPIO.HIGH) +GPIO.setup(11, GPIO.OUT) +GPIO.output(11, GPIO.HIGH) + + +PASSWORD_HASH = 'CHANGE TO A SHA256 HASH' + +# Example to generate password Hash - Example below is not the actual Password +# print(generate_password_hash('test','sha256')) + + + +def checkGaragePassword(input_password): + return check_password_hash(PASSWORD_HASH,input_password) + + +def triggerGarage(): + GPIO.output(7, GPIO.LOW) + time.sleep(1) + GPIO.output(7, GPIO.HIGH) + print("Garage Triggered") + time.sleep(12) + + +def checkGarageStatus(): + if GPIO.input(18) == GPIO.LOW: + print("Garage is Open") + return 'Open' + else: + print("Garage is Closed or Opening/Closing") + return 'Question' + + +def garageCamera(): + camera = cv2.VideoCapture('http://192.168.68.118:4747/video') + + while True: + success, frame = camera.read() # read the camera frame + + if not success: + break + else: + ret, buffer = cv2.imencode('.jpg', frame) + frame = buffer.tobytes() + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') # concat frame one by one and show result + time.sleep(4) \ No newline at end of file diff --git a/log.py b/log.py index 695d44f..93d5b09 100644 --- a/log.py +++ b/log.py @@ -4,12 +4,14 @@ from datetime import datetime -logfile = open("/home/pi/GarageWeb/static/log.txt","a") -logfile.write(datetime.now().strftime(" Program Starting -- %Y/%m/%d -- %H:%M -- Hello! \n")) +logfile = open("/home/piwhite/Documents/github/GarageControl/static/log.txt", "a") +logfile.write(datetime.now().strftime( + " Program Starting -- %Y/%m/%d -- %H:%M -- Hello! \n")) logfile.close() -print(datetime.now().strftime(" Program Starting -- %Y/%m/%d -- %H:%M -- Hello! \n")) +print(datetime.now().strftime( + " Program Starting -- %Y/%m/%d -- %H:%M -- Hello! \n")) -print " Control + C to exit Program" +print("Control + C to exit Program") GPIO.setmode(GPIO.BOARD) GPIO.setwarnings(False) @@ -18,48 +20,50 @@ GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) time.sleep(1) -TimeDoorOpened = datetime.strptime(datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S'),'%Y-%m-%d %H:%M:%S') #Default Time -DoorOpenTimer = 0 #Default start status turns timer off -DoorOpenTimerMessageSent = 1 #Turn off messages until timer is started +TimeDoorOpened = datetime.strptime(datetime.strftime( + datetime.now(), '%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') # Default Time +DoorOpenTimer = 0 # Default start status turns timer off +DoorOpenTimerMessageSent = 1 # Turn off messages until timer is started try: - while 1 >= 0: - time.sleep(1) - if DoorOpenTimer == 1: #Door Open Timer has Started - currentTimeDate = datetime.strptime(datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S'),'%Y-%m-%d %H:%M:%S') - if (currentTimeDate - TimeDoorOpened).seconds > 900 and DoorOpenTimerMessageSent == 0: - print "Your Garage Door has been Open for 15 minutes" - DoorOpenTimerMessageSent = 1 + while 1 >= 0: + time.sleep(1) + if DoorOpenTimer == 1: # Door Open Timer has Started + currentTimeDate = datetime.strptime(datetime.strftime( + datetime.now(), '%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') + if (currentTimeDate - TimeDoorOpened).seconds > 900 and DoorOpenTimerMessageSent == 0: + print("Your Garage Door has been Open for 15 minutes") + DoorOpenTimerMessageSent = 1 - if GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: #Door Status is Unknown - logfile = open("/home/pi/GarageWeb/static/log.txt","a") - logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Opening/Closing \n")) - logfile.close() - print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Opening/Closing \n")) - while GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: - time.sleep(.5) - else: - if GPIO.input(16) == GPIO.LOW: #Door is Closed - logfile = open("/home/pi/GarageWeb/static/log.txt","a") - logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Closed \n")) - logfile.close() - print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Closed")) - DoorOpenTimer = 0 + if GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: #Door Status is Unknown + logfile = open("/home/piwhite/Documents/github/GarageControl/static/log.txt","a") + logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Opening/Closing \n")) + logfile.close() + print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Opening/Closing \n")) + while GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: + time.sleep(.5) + else: + if GPIO.input(16) == GPIO.LOW: #Door is Closed + logfile = open("/home/piwhite/Documents/github/GarageControl/static/log.txt","a") + logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Closed \n")) + logfile.close() + print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Closed")) + DoorOpenTimer = 0 - if GPIO.input(18) == GPIO.LOW: #Door is Open - logfile = open("/home/pi/GarageWeb/static/log.txt","a") - logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Open \n")) - logfile.close() - print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Open")) - #Start Door Open Timer - TimeDoorOpened = datetime.strptime(datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S'),'%Y-%m-%d %H:%M:%S') - DoorOpenTimer = 1 - DoorOpenTimerMessageSent = 0 + if GPIO.input(18) == GPIO.LOW: #Door is Open + logfile = open("/home/piwhite/Documents/github/GarageControl/static/log.txt","a") + logfile.write(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Open \n")) + logfile.close() + print(datetime.now().strftime("%Y/%m/%d -- %H:%M:%S -- Door Open")) + #Start Door Open Timer + TimeDoorOpened = datetime.strptime(datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S'),'%Y-%m-%d %H:%M:%S') + DoorOpenTimer = 1 + DoorOpenTimerMessageSent = 0 except KeyboardInterrupt: - logfile = open("/home/pi/GarageWeb/static/log.txt","a") - logfile.write(datetime.now().strftime(" Log Program Shutdown -- %Y/%m/%d -- %H:%M -- Goodbye! \n")) - logfile.close() - print(datetime.now().strftime(" Log Program Shutdown -- %Y/%m/%d -- %H:%M -- Goodbye! \n")) - GPIO.cleanup() + logfile = open("/home/piwhite/Documents/github/GarageControl/static/log.txt","a") + logfile.write(datetime.now().strftime(" Log Program Shutdown -- %Y/%m/%d -- %H:%M -- Goodbye! \n")) + logfile.close() + print(datetime.now().strftime(" Log Program Shutdown -- %Y/%m/%d -- %H:%M -- Goodbye! \n")) + GPIO.cleanup() diff --git a/relaytest.py b/relaytest.py index 0b29a87..131e428 100644 --- a/relaytest.py +++ b/relaytest.py @@ -1,4 +1,4 @@ -print " Control + C to exit Program" +print(" Control + C to exit Program") import time @@ -9,26 +9,11 @@ GPIO.output(7, GPIO.HIGH) # sets the pin output to high GPIO.setup(11, GPIO.OUT) GPIO.output(11, GPIO.HIGH) -GPIO.setup(13, GPIO.OUT) -GPIO.output(13, GPIO.HIGH) -GPIO.setup(15, GPIO.OUT) -GPIO.output(15, GPIO.HIGH) try: - while 1 >=0: - GPIO.output(7, GPIO.LOW) # turns the first relay switch ON - time.sleep(.5) # pauses system for 1/2 second - GPIO.output(7, GPIO.HIGH) # turns the first relay switch OFF - GPIO.output(11, GPIO.LOW) # turns the second relay switch ON - time.sleep(.5) - GPIO.output(11, GPIO.HIGH) - GPIO.output(13, GPIO.LOW) - time.sleep(.5) - GPIO.output(13, GPIO.HIGH) - GPIO.output(15, GPIO.LOW) - time.sleep(.5) - GPIO.output(15, GPIO.HIGH) - time.sleep(.5) + GPIO.output(7, GPIO.LOW) # turns the first relay switch ON + time.sleep(.5) # pauses system for 1/2 second + GPIO.output(7, GPIO.HIGH) # turns the first relay switch OFF except KeyboardInterrupt: # Stops program when "Control + C" is entered GPIO.cleanup() # Turns OFF all relay switches diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..02cf3cc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +bcrypt==4.0.1 +click==8.1.3 +dnspython==2.3.0 +email-validator==2.0.0.post1 +Flask==2.2.3 +Flask-Bcrypt==1.0.1 +Flask-Login==0.6.2 +Flask-SQLAlchemy==3.0.3 +Flask-WTF==1.1.1 +idna==3.4 +importlib-metadata==6.5.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.2 +numpy==1.24.2 +opencv-python==4.5.3.56 +pygame==2.3.0 +RPi.GPIO==0.7.1 +SQLAlchemy==2.0.9 +typing_extensions==4.5.0 +Werkzeug==2.2.3 +WTForms==3.0.1 +zipp==3.15.0 diff --git a/static/Closed.html b/static/Closed.html index ee9425b..ba33a07 100644 --- a/static/Closed.html +++ b/static/Closed.html @@ -6,9 +6,6 @@

-
- -


Click for Log File

diff --git a/static/Open.html b/static/Open.html index c4dfa6c..223da1d 100644 --- a/static/Open.html +++ b/static/Open.html @@ -6,9 +6,6 @@

-
- -


Click for Log File

diff --git a/static/Question.html b/static/Question.html index e878681..0884d35 100644 --- a/static/Question.html +++ b/static/Question.html @@ -3,13 +3,10 @@ - +

-
- -
- +

Click for Log File

diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..5590d8d --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,64 @@ + + + + + + + Dashboard + + + +
+
+
+

Garage Stream (delayed 10seconds)

+ +
+
+ {% if status=="Open" %} + + {% else %} + + {% endif %} +
+
+ +
+
+
+
+ +
+ Logout +
+
+
+ + + \ No newline at end of file diff --git a/templates/garageCamera.html b/templates/garageCamera.html new file mode 100644 index 0000000..c72e23c --- /dev/null +++ b/templates/garageCamera.html @@ -0,0 +1,19 @@ + + + + + + + Garage Camera + + +
+
+
+

Live Streaming

+ +
+
+
+ + \ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..201cb62 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,13 @@ + + + + + + + Home + + +

Login Auth in Flask

+ Login Page + + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..9bab3ba --- /dev/null +++ b/templates/login.html @@ -0,0 +1,21 @@ + + + + + + + Login + + +

Login Page

+
+ {{ form.hidden_tag() }} + {{ form.password }} + {{ form.submit }} +
+ {% if error %} +

Error: {{error}}

+ {% endif %} + + + \ No newline at end of file diff --git a/web.py b/web.py index 9d8f4df..af742fa 100644 --- a/web.py +++ b/web.py @@ -1,86 +1,87 @@ -import time -from datetime import datetime -from flask import Flask, render_template, request +from flask import Flask, render_template, url_for, redirect, Response, request, session +from flask_wtf import FlaskForm +from wtforms import PasswordField, SubmitField +from wtforms.validators import InputRequired, ValidationError +from flask_login import login_user, LoginManager, login_required, logout_user, current_user -import RPi.GPIO as GPIO -GPIO.setmode(GPIO.BOARD) # the pin numbers refer to the board connector not the chip -GPIO.setwarnings(False) -GPIO.setup(16, GPIO.IN, GPIO.PUD_UP) # set up pin ?? (one of the above listed pins) as an input with a pull-up resistor -GPIO.setup(18, GPIO.IN, GPIO.PUD_UP) # set up pin ?? (one of the above listed pins) as an input with a pull-up resistor -GPIO.setup(7, GPIO.OUT) -GPIO.output(7, GPIO.HIGH) -GPIO.setup(11, GPIO.OUT) -GPIO.output(11, GPIO.HIGH) -GPIO.setup(13, GPIO.OUT) -GPIO.output(13, GPIO.HIGH) -GPIO.setup(15, GPIO.OUT) -GPIO.output(15, GPIO.HIGH) +from data import USERS +from garageFunctions import checkGaragePassword, checkGarageStatus, triggerGarage, garageCamera +app = Flask(__name__) +app.config['SECRET_KEY'] = 'SUPERSAFESECRETKEY-- Change me' +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view="login" -app = Flask(__name__) +class LoginForm(FlaskForm): + password = PasswordField(validators=[InputRequired()], + render_kw={"placeholder" : "password"}) + submit = SubmitField("Login") -@app.route('/', methods=['GET', 'POST']) -def index(): - if GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: - print("Garage is Opening/Closing") - return app.send_static_file('Question.html') - else: - if GPIO.input(16) == GPIO.LOW: - print ("Garage is Closed") - return app.send_static_file('Closed.html') - if GPIO.input(18) == GPIO.LOW: - print ("Garage is Open") - return app.send_static_file('Open.html') - - -@app.route('/Garage', methods=['GET', 'POST']) -def Garage(): - name = request.form['garagecode'] - if name == '12345678': # 12345678 is the Password that Opens Garage Door (Code if Password is Correct) - GPIO.output(7, GPIO.LOW) - time.sleep(1) - GPIO.output(7, GPIO.HIGH) - time.sleep(2) - - if GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: - print("Garage is Opening/Closing") - return app.send_static_file('Question.html') - else: - if GPIO.input(16) == GPIO.LOW: - print ("Garage is Closed") - return app.send_static_file('Closed.html') - if GPIO.input(18) == GPIO.LOW: - print ("Garage is Open") - return app.send_static_file('Open.html') - - if name != '12345678': # 12345678 is the Password that Opens Garage Door (Code if Password is Incorrect) - if name == "": - name = "NULL" - print("Garage Code Entered: " + name) - if GPIO.input(16) == GPIO.HIGH and GPIO.input(18) == GPIO.HIGH: - print("Garage is Opening/Closing") - return app.send_static_file('Question.html') - else: - if GPIO.input(16) == GPIO.LOW: - print ("Garage is Closed") - return app.send_static_file('Closed.html') - if GPIO.input(18) == GPIO.LOW: - print ("Garage is Open") - return app.send_static_file('Open.html') - -@app.route('/stylesheet.css') -def stylesheet(): - return app.send_static_file('stylesheet.css') - -@app.route('/Log') -def logfile(): - return app.send_static_file('log.txt') +class TriggerForm(FlaskForm): + trigger = SubmitField("Trigger Garage") + +USER_ID = "1221" + +@login_manager.user_loader +def load_user(user_id=USER_ID): + return USERS.get(str(user_id)) + +@app.route('/') +def home(): + return redirect(url_for('login')) + +@app.route('/login', methods=['GET','POST']) +def login(): + form = LoginForm() + error = None + if form.validate_on_submit(): + if checkGaragePassword(form.password.data): + login_user(USERS.get(str(USER_ID))) + return redirect(url_for('dashboard')) + else: + error = 'Invalid Credentials' + + return render_template('login.html', form=form, error=error) -@app.route('/images/') -def images(picture): - return app.send_static_file('images/' + picture) +@app.route('/dashboard', methods=['GET','POST']) +@login_required +def dashboard(): + + if request.method == 'POST': + if request.form['trigger'] == 'Trigger Garage': + triggerGarage() + print("garage triggered") + status = checkGarageStatus() + return render_template('dashboard.html', status=status) + + +@app.route('/garagecamera', methods=['GET','POST']) +@login_required +def garagecamera(): + return render_template('garageCamera.html') + +@app.route('/camera', methods=['GET','POST']) +@login_required +def camera(): + return Response(garageCamera(), mimetype='multipart/x-mixed-replace; boundary=frame') + +@app.route('/triggerremotegarage', methods=['GET','POST']) +@login_required +def triggerremotegarage(): + triggerGarage() + return redirect(url_for('dashboard')) + +@app.route('/log') +def logfile(): + return app.send_static_file('log.txt') +@app.route('/logout', methods=['GET','POST']) +def logout(): + logout_user() + return redirect(url_for('login')) + if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=5000) + app.run(host='0.0.0.0', port=5000) \ No newline at end of file