Website is pretty much done. Did a bit of off screen mining
This commit is contained in:
parent
2dd3b711f7
commit
837a3f09de
6
.gitignore
vendored
6
.gitignore
vendored
@ -4,6 +4,12 @@ __pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Django migrations
|
||||
migrations/
|
||||
|
||||
# sqlite3 db
|
||||
*.sqlite3
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
8
src/arduino/.vscode/arduino.json
vendored
Normal file
8
src/arduino/.vscode/arduino.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"port": "/dev/ttyS0",
|
||||
"configuration": "PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=none",
|
||||
"board": "esp32:esp32:esp32da",
|
||||
"programmer": "esptool",
|
||||
"sketch": "text_display.ino",
|
||||
"output": "../ArduinoOutput"
|
||||
}
|
105
src/arduino/text_display.ino
Normal file
105
src/arduino/text_display.ino
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
// Example sketch which shows how to display some patterns
|
||||
// on a 64x32 LED matrix
|
||||
//
|
||||
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
|
||||
|
||||
#define PANEL_RES_X 64 // Number of pixels wide of each INDIVIDUAL panel module.
|
||||
#define PANEL_RES_Y 32 // Number of pixels tall of each INDIVIDUAL panel module.
|
||||
#define PANEL_CHAIN 1 // Total number of panels chained one to another
|
||||
|
||||
//MatrixPanel_I2S_DMA dma_display;
|
||||
MatrixPanel_I2S_DMA *dma_display = nullptr;
|
||||
|
||||
const char *firstLine = "obicham te";
|
||||
const char *secondLine = "mnogo, bebi";
|
||||
const char *thirdLine = "bottom text";
|
||||
|
||||
|
||||
uint16_t myBLACK = dma_display->color565(0, 0, 0);
|
||||
uint16_t myWHITE = dma_display->color565(255, 255, 255);
|
||||
uint16_t myRED = dma_display->color565(255, 0, 0);
|
||||
uint16_t myGREEN = dma_display->color565(0, 255, 0);
|
||||
uint16_t myBLUE = dma_display->color565(0, 0, 255);
|
||||
|
||||
|
||||
|
||||
// Input a value 0 to 255 to get a color value.
|
||||
// The colours are a transition r - g - b - back to r.
|
||||
// From: https://gist.github.com/davidegironi/3144efdc6d67e5df55438cc3cba613c8
|
||||
uint16_t colorWheel(uint8_t pos) {
|
||||
if(pos < 85) {
|
||||
return dma_display->color565(pos * 3, 255 - pos * 3, 0);
|
||||
} else if(pos < 170) {
|
||||
pos -= 85;
|
||||
return dma_display->color565(255 - pos * 3, 0, pos * 3);
|
||||
} else {
|
||||
pos -= 170;
|
||||
return dma_display->color565(0, pos * 3, 255 - pos * 3);
|
||||
}
|
||||
}
|
||||
|
||||
void drawText(int colorWheelOffset)
|
||||
{
|
||||
|
||||
// draw text with a rotating colour
|
||||
dma_display->setTextSize(1); // size 1 == 8 pixels high
|
||||
dma_display->setTextWrap(false); // Don't wrap at end of line - will do ourselves
|
||||
|
||||
dma_display->setCursor(0, 0); // start at top left
|
||||
uint8_t w = 0;
|
||||
for (w=0; w<strlen(firstLine); w++) {
|
||||
dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
|
||||
dma_display->print(firstLine[w]);
|
||||
}
|
||||
|
||||
dma_display->println();
|
||||
for (w=9; w<20; w++) {
|
||||
dma_display->setTextColor(colorWheel((w*32)+colorWheelOffset));
|
||||
dma_display->print("=");
|
||||
}
|
||||
|
||||
dma_display->println();
|
||||
|
||||
dma_display->setTextColor(dma_display->color444(15,15,15));
|
||||
dma_display->println(secondLine); // 10 char max
|
||||
dma_display->println(thirdLine);
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
// Module configuration
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
PANEL_RES_X, // module width
|
||||
PANEL_RES_Y, // module height
|
||||
PANEL_CHAIN // Chain length
|
||||
);
|
||||
|
||||
|
||||
// Display Setup
|
||||
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
dma_display->begin();
|
||||
dma_display->setBrightness8(90); //0-255
|
||||
dma_display->clearScreen();
|
||||
dma_display->fillScreen(myWHITE);
|
||||
|
||||
// fill the screen with 'black'
|
||||
dma_display->fillScreen(dma_display->color444(0, 0, 0));
|
||||
|
||||
}
|
||||
|
||||
uint8_t wheelval = 0;
|
||||
|
||||
void loop() {
|
||||
|
||||
// animate by going through the colour wheel for the first two lines
|
||||
drawText(wheelval);
|
||||
wheelval +=1;
|
||||
|
||||
delay(20);
|
||||
|
||||
|
||||
}
|
22
src/place/manage.py
Executable file
22
src/place/manage.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'place.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
src/place/place/__init__.py
Normal file
0
src/place/place/__init__.py
Normal file
16
src/place/place/asgi.py
Normal file
16
src/place/place/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for place project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'place.settings')
|
||||
|
||||
application = get_asgi_application()
|
124
src/place/place/settings.py
Normal file
124
src/place/place/settings.py
Normal file
@ -0,0 +1,124 @@
|
||||
"""
|
||||
Django settings for place project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-)1r%_dlx4^+o8sg#z8vm-mq+8oslg=v2*(2zpc6-y+$_ntm4%!'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
"website",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'place.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'place.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
25
src/place/place/urls.py
Normal file
25
src/place/place/urls.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""
|
||||
URL configuration for place project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from website import views
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('board/', views.current_config),
|
||||
path('board/<int:x>/<int:y>/<str:color>', views.update_board),
|
||||
]
|
16
src/place/place/wsgi.py
Normal file
16
src/place/place/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for place project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'place.settings')
|
||||
|
||||
application = get_wsgi_application()
|
0
src/place/website/__init__.py
Normal file
0
src/place/website/__init__.py
Normal file
5
src/place/website/admin.py
Normal file
5
src/place/website/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from .models import Board, RestrictedIP
|
||||
# Register your models here.
|
||||
admin.site.register(Board)
|
||||
admin.site.register(RestrictedIP)
|
6
src/place/website/apps.py
Normal file
6
src/place/website/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WebsiteConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'website'
|
0
src/place/website/migrations/__init__.py
Normal file
0
src/place/website/migrations/__init__.py
Normal file
15
src/place/website/models.py
Normal file
15
src/place/website/models.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.db import models
|
||||
|
||||
class Board(models.Model):
|
||||
update_time = models.DateTimeField(unique=True, primary_key=True)
|
||||
configuration = models.JSONField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.update_time}"
|
||||
|
||||
class RestrictedIP(models.Model):
|
||||
ip = models.GenericIPAddressField(unique=True, primary_key=True)
|
||||
time_added = models.DateTimeField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.ip}"
|
3
src/place/website/tests.py
Normal file
3
src/place/website/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
155
src/place/website/views.py
Normal file
155
src/place/website/views.py
Normal file
@ -0,0 +1,155 @@
|
||||
from django.shortcuts import render
|
||||
from .models import Board, RestrictedIP
|
||||
from datetime import datetime
|
||||
from django.http import HttpResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from colour import Color
|
||||
from pytz import UTC
|
||||
# Change these for your board
|
||||
BOARD_X = 64
|
||||
BOARD_Y = 32
|
||||
|
||||
# Options for implementation of board update:
|
||||
# 1) Serve at a special url which is a OTC generated by the server and the client (Hard-ish)
|
||||
# 2) Serve at all times and have the client decide when it has changed(Easy)
|
||||
|
||||
|
||||
|
||||
# Internal validation
|
||||
def validateBoard(x:int, y:int, board:dict, ip) -> bool:
|
||||
try:
|
||||
ip = RestrictedIP.objects.get(ip=ip)
|
||||
return "RESTRICTED"
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
key_counter = 0 # Not using len for optimization purposes
|
||||
|
||||
for row in board:
|
||||
# Check if datatype is list
|
||||
if not isinstance(board[row], list):
|
||||
return "WRONG Y"
|
||||
|
||||
# If length of list is not equal to given width of matrix, it is incorrect
|
||||
length = len(board[row])
|
||||
if length != x:
|
||||
return "WRONG LENGTH"
|
||||
|
||||
key_counter += 1
|
||||
|
||||
# Check if the keys are the same number as y
|
||||
if key_counter != y:
|
||||
return "WRONG X"
|
||||
return "SUCCESS"
|
||||
|
||||
def restrict_or_unrestrict(ip):
|
||||
try:
|
||||
ip = RestrictedIP.objects.get(ip=ip)
|
||||
timedelta = datetime.utcnow().replace(tzinfo=UTC) - ip.time_added
|
||||
# if banned for more than 1 minute, unban
|
||||
if timedelta.seconds > 60:
|
||||
ip.delete()
|
||||
return "ALLOWED"
|
||||
return "STILL RESTRICTED"
|
||||
except ObjectDoesNotExist:
|
||||
instance = RestrictedIP(ip=ip, time_added=datetime.now())
|
||||
instance.save()
|
||||
return "RESTRICTION"
|
||||
|
||||
# Internal board class
|
||||
class localBoard:
|
||||
def __init__(self) -> None:
|
||||
try:
|
||||
self.board = Board.objects.latest("update_time")
|
||||
except ObjectDoesNotExist:
|
||||
# Create a blank board
|
||||
blank_config = {int(i): [-1 for _ in range(64)] for i in range(32)}
|
||||
|
||||
# Time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format
|
||||
time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
instance = Board(update_time=time, configuration=blank_config)
|
||||
instance.save()
|
||||
self.board = Board.objects.latest("update_time")
|
||||
|
||||
def getBoard(self) -> dict:
|
||||
return dict(self.board.configuration)
|
||||
|
||||
def setBoard(self, new_configuration:dict, ip) -> bool:
|
||||
# Validate the board
|
||||
if err := validateBoard(x=BOARD_X, y=BOARD_Y, board=new_configuration, ip=ip) != "SUCCESS":
|
||||
return err
|
||||
|
||||
# Save the board
|
||||
instance = Board(update_time=datetime.now(), configuration=new_configuration)
|
||||
instance.save()
|
||||
self.board = Board.objects.latest("update_time")
|
||||
return "SUCCESS"
|
||||
|
||||
def HTML(self) -> str:
|
||||
board = self.getBoard()
|
||||
style = """
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; height: 90vh; border: 1px solid white;}
|
||||
td { width: 10px; height: 10px; border: 1px solid gray; }
|
||||
td:onclick { border: 2px solid white; }
|
||||
td:hover { border: 2px solid white; }
|
||||
.color {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
selected_pixel = """
|
||||
<script>
|
||||
async function selected_pixel(element) {
|
||||
var color = document.getElementById("color_picker").value;
|
||||
|
||||
var arr = element.id.split("x")[1].split("y");
|
||||
var x = arr[0];
|
||||
var y = arr[1];
|
||||
|
||||
var url = "http://127.0.0.1:8000/board/" + x + "/" + y + "/" + color.replace("#", "%23");
|
||||
let response = await fetch(url);
|
||||
let result = await response.text();
|
||||
console.log(result);
|
||||
if (result != "RESTRICTED") {
|
||||
element.style.backgroundColor = color;
|
||||
}
|
||||
else {
|
||||
alert("You have placed your pixel. Please wait.");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
html = f"<html><head><title>Ballin</title></head>{style}<body>{selected_pixel}<table><form>"
|
||||
for row in board:
|
||||
html += "<tr>"
|
||||
x = 0
|
||||
for col in board[row]:
|
||||
html += f"<td style='background-color:{col};' id={'x'+ str(x) + 'y'+ str(row)} onclick=selected_pixel(this)></td>"
|
||||
x += 1
|
||||
html += "</tr>"
|
||||
html += "</table><div class='color'><input type='color' id='color_picker'><p color='white' ><------ pick ur color</p></div></body></html>"
|
||||
return html
|
||||
|
||||
# Publicly visible functions
|
||||
def update_board(request, x, y, color):
|
||||
board = localBoard().getBoard()
|
||||
try:
|
||||
color = Color(color).hex
|
||||
except Exception as e:
|
||||
return HttpResponse(e)
|
||||
board[str(y)][int(x)] = color
|
||||
|
||||
ip = restrict_or_unrestrict(request.META["REMOTE_ADDR"])
|
||||
if ip == "STILL RESTRICTED":
|
||||
return HttpResponse("RESTRICTED")
|
||||
res = localBoard().setBoard(board, request.META["REMOTE_ADDR"])
|
||||
return HttpResponse(f"board: {str(res)}, ip: {ip}")
|
||||
|
||||
def current_config(request):
|
||||
html = localBoard().HTML()
|
||||
return HttpResponse(html)
|
||||
|
Loading…
x
Reference in New Issue
Block a user