diff --git a/.gitignore b/.gitignore index 5d381cc..413f7ee 100644 --- a/.gitignore +++ b/.gitignore @@ -128,6 +128,8 @@ venv/ ENV/ env.bak/ venv.bak/ +bin/ +pyvenv.cfg # Spyder project settings .spyderproject @@ -160,3 +162,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# ignore socket which is used to communicate with backend +rgb_socket diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..44ef5ef --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,32 @@ +from flask import Flask, render_template +import socket +import os +import json + +app = Flask(__name__) + + +@app.route('/') +def index(): + return render_template('index.html') + + +@app.route('/color/', methods=['POST']) +def color(color): + socket_path = '/var/www/vhosts/rgb.local/rgb_socket' + if os.path.exists(socket_path): + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect(socket_path) + client.sendall(color.encode()) + client.close() + return (json.dumps({'success': True}), + 200, + {'ContentType': 'application/json'}) + else: + return (json.dumps({'success': False}), + 500, + {'ContentType': 'application/json'}) + + +if __name__ == "__main__": + app.run() diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..6c478c5 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,9 @@ +blinker==1.6.2 +click==8.1.3 +Flask==2.3.2 +importlib-metadata==6.6.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.2 +Werkzeug==2.3.4 +zipp==3.15.0 diff --git a/app/static/app.css b/app/static/app.css new file mode 100644 index 0000000..3f3c516 --- /dev/null +++ b/app/static/app.css @@ -0,0 +1,62 @@ +html { + font-family: sans-serif; +} + +h1 { + text-align: center; +} + +div.container { + display: flex; + flex-wrap: wrap; +} + +div.center { + margin: auto; + max-width: 500px; +} + +a.color { + display: block; + width: calc(100vw - 24px); + height: 100px; + border: 1px solid black; + text-align: center; + vertical-align: middle; + line-height:100px; + font-size:18px; + font-weight: bold; + cursor: pointer; + margin-bottom: 16px; + border-radius: 12px; +} + +a.red { + background-color: #FF0000; +} +a.green { + background-color: #00FF00; +} +a.blue { + background-color: #0000FF; + color: #FFFFFF; +} +a.cyan { + background-color: #00FFFF; +} +a.yellow { + background-color: #FFFF00; +} +a.orange { + background-color: #FF8000; +} +a.purple { + background-color: #FF00FF; +} +a.white { + background-color: #FFFFFF; +} +a.black { + background-color: #000000; + color: #FFFFFF; +} diff --git a/app/static/app.js b/app/static/app.js new file mode 100644 index 0000000..6fd7a0c --- /dev/null +++ b/app/static/app.js @@ -0,0 +1,12 @@ +document.addEventListener("DOMContentLoaded", () => { + + document.querySelectorAll("a.color").forEach(button => { + button.addEventListener("click", event => { + console.log("button"); + console.log(button.dataset.color); + fetch("/color/" + button.dataset.color, {method: "POST"}); + }); + }); + +}); + diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..4755b2f --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,20 @@ + + + + + RGB {% block title %}{% endblock %} + + + + + + +
+

RGB controller

+
+ {% block content %}{% endblock %} +
+
+ + + diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..a565bc6 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %} yay {% endblock %} + +{% block content %} + +
+ red + green + blue + cyan + yellow + orange + purple + white + off +
+{% endblock %} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6c478c5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +blinker==1.6.2 +click==8.1.3 +Flask==2.3.2 +importlib-metadata==6.6.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.2 +Werkzeug==2.3.4 +zipp==3.15.0 diff --git a/rgb.py b/rgb.py new file mode 100755 index 0000000..6fe00f4 --- /dev/null +++ b/rgb.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +import socket +import os +import re +import time +from rpi_ws281x import Adafruit_NeoPixel, Color + + +# Define functions which animate LEDs in various ways. +def colorWipe(strip, color, wait_ms=50): + """Wipe color across display a pixel at a time.""" + for i in range(strip.numPixels()): + strip.setPixelColor(i, color) + strip.show() + time.sleep(wait_ms / 1000.0) + + +if __name__ == "__main__": + TOTAL_LED_COUNT = 24 + R = 0 + G = 0 + B = 0 + + strip = Adafruit_NeoPixel(TOTAL_LED_COUNT, 18, 800000, 5, False, 255) + strip.begin() + colorWipe(strip, Color(0, 0, 0)) + + # Set the path for the Unix socket + socket_path = '/var/www/vhosts/rgb.local/rgb_socket' + + # remove the socket file if it already exists + try: + os.unlink(socket_path) + except OSError: + if os.path.exists(socket_path): + raise + + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + server.bind(socket_path) + server.listen(1) + os.chown(socket_path, 33, 33) + connection, client_address = server.accept() + + try: + while True: + data = connection.recv(1024) + if not data: + connection, client_address = server.accept() + continue + rgb = data.decode() + if re.match("^[0-9A-Fa-f]{6}$", rgb): + colorWipe(strip, Color( + int(rgb[0:2], 16), + int(rgb[2:4], 16), + int(rgb[4:6], 16)), + 10 + ) + finally: + colorWipe(strip, Color(0, 0, 0), 10) + connection.close() + os.unlink(socket_path) diff --git a/rgb.service b/rgb.service new file mode 100644 index 0000000..73ae54c --- /dev/null +++ b/rgb.service @@ -0,0 +1,12 @@ +[Unit] +Description=RGB Service + +[Service] +Type=simple +User=root +ExecStart=/usr/local/bin/rgb.py +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/rgb.wsgi b/rgb.wsgi new file mode 100644 index 0000000..cf190ea --- /dev/null +++ b/rgb.wsgi @@ -0,0 +1,14 @@ +#!/usr/bin/python +import os +import sys +import logging +logging.basicConfig(stream=sys.stderr) +sys.path.insert(0, os.path.dirname(__file__)) +activate_this = os.path.join(os.path.dirname(__file__), 'bin/activate_this.py') +with open(activate_this) as file_: + exec(file_.read(), dict(__file__=activate_this)) + + + +from app import app as application +application.secret_key = 'something super SUPER secret'