Old SandTable project
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
|
||||
default_app_config = 'sandtable.apps.SandtableConfig'
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SandtableConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'sandtable'
|
||||
|
||||
def ready(self):
|
||||
# Startup code here
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from django import forms
|
||||
from .models import *
|
||||
|
||||
|
||||
class UploadFileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Tracks
|
||||
fields = ('file', )
|
||||
|
||||
|
||||
class AddToQueueForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Queue
|
||||
fields = ()
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 4.0 on 2022-01-23 13:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Queue',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.CharField(max_length=200)),
|
||||
('track_length', models.IntegerField()),
|
||||
('pic', models.CharField(max_length=200)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tracks',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('file', models.FileField(upload_to='tracks/')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('pic', models.ImageField(upload_to='')),
|
||||
('track_length', models.IntegerField(default=0)),
|
||||
('uploaded_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.0 on 2022-01-30 09:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RGBW',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('r', models.IntegerField(default=0)),
|
||||
('g', models.IntegerField(default=0)),
|
||||
('b', models.IntegerField(default=0)),
|
||||
('w', models.IntegerField(default=0)),
|
||||
('led_track', models.CharField(default='None', max_length=100)),
|
||||
('changed', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.0 on 2022-02-13 14:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0002_rgbw'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Settings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('motor_speed', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rgbw',
|
||||
name='led_speed',
|
||||
field=models.IntegerField(default=100),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.0 on 2022-10-14 20:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0003_settings_rgbw_led_speed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rgbw',
|
||||
name='led_intensity',
|
||||
field=models.IntegerField(default=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rgbw',
|
||||
name='led_saturation',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rgbw',
|
||||
name='led_speed',
|
||||
field=models.IntegerField(default=1),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='settings',
|
||||
name='motor_speed',
|
||||
field=models.IntegerField(default=1),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.0 on 2022-10-16 11:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0004_rgbw_led_intensity_rgbw_led_saturation_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='settings',
|
||||
name='r',
|
||||
field=models.FloatField(default=0.0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='settings',
|
||||
name='theta',
|
||||
field=models.FloatField(default=0.0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.3 on 2022-12-01 21:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0005_settings_r_settings_theta'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='settings',
|
||||
name='calibrate',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-06 19:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sandtable', '0006_settings_calibrate'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rgbw',
|
||||
name='led_track',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# Creating track entries
|
||||
class Tracks(models.Model):
|
||||
file = models.FileField(upload_to='tracks/')
|
||||
name = models.CharField(max_length=200)
|
||||
pic = models.ImageField()
|
||||
track_length = models.IntegerField(default=0)
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
# Creating entries for the queue
|
||||
class Queue(models.Model):
|
||||
file = models.CharField(max_length=200)
|
||||
track_length = models.IntegerField()
|
||||
pic = models.CharField(max_length=200)
|
||||
|
||||
|
||||
# Putting LED strip values to database
|
||||
class RGBW(models.Model):
|
||||
r = models.IntegerField(default=0)
|
||||
g = models.IntegerField(default=0)
|
||||
b = models.IntegerField(default=0)
|
||||
w = models.IntegerField(default=0)
|
||||
led_track = models.IntegerField(default=0)
|
||||
changed = models.BooleanField(default=False)
|
||||
led_speed = models.IntegerField(default=1)
|
||||
led_intensity = models.IntegerField(default=100)
|
||||
led_saturation = models.IntegerField(default=0)
|
||||
|
||||
|
||||
# Settings
|
||||
class Settings(models.Model):
|
||||
motor_speed = models.IntegerField(default=1)
|
||||
r = models.FloatField(default=0.0)
|
||||
theta = models.FloatField(default=0.0)
|
||||
calibrate = models.IntegerField(default=0)
|
||||
@@ -0,0 +1,323 @@
|
||||
:root {
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans';
|
||||
--text-primary: #b6b6b6;
|
||||
--text-secondary: #ececec;
|
||||
--bg-primary: #121212;
|
||||
--bg-secondary: #222222;
|
||||
}
|
||||
body {
|
||||
background-color: var(--bg-primary);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
.header{
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
height: 8rem;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.center a {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
|
||||
/* Navigation bar */
|
||||
.nav {
|
||||
width: 100%;
|
||||
height: 8rem;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
ul.nav-list {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-left: -50px;
|
||||
margin-top: 0px;
|
||||
height: 95%;
|
||||
list-style-type: none;
|
||||
align-items: center;
|
||||
}
|
||||
ul.nav-list li {
|
||||
width: 25%;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
ul.nav-list .icon {
|
||||
font-size: 70px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
|
||||
/* Index page */
|
||||
main.index {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 85%;
|
||||
}
|
||||
#canvas-div {
|
||||
visibility: visible;
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
width: 90%;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
margin-bottom: 0;
|
||||
margin-top: 8rem;
|
||||
}
|
||||
.track-playing {
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.track-playing p {
|
||||
margin: 0;
|
||||
}
|
||||
.media {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
.media a {
|
||||
width: 33%;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.media .icon {
|
||||
font-size: 70px;
|
||||
}
|
||||
.media #play .icon {
|
||||
font-size: 200px;
|
||||
}
|
||||
.pause {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Tracks page */
|
||||
main.tracks {
|
||||
height: 100%;
|
||||
}
|
||||
.header-list {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.header-list li {
|
||||
text-align: center;
|
||||
}
|
||||
.header-list a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 50px;
|
||||
}
|
||||
.track-list-div {
|
||||
padding-top: 8rem;
|
||||
padding-bottom: 12rem;
|
||||
}
|
||||
ul.track-list {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0px;
|
||||
list-style-type: none;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
ul.track-list li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
width: 50%;
|
||||
}
|
||||
ul.track-list a {
|
||||
margin-left: 15%;
|
||||
margin-right: 15%;
|
||||
}
|
||||
ul.track-list .list-pic {
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
}
|
||||
ul.track-list .track-name {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
font-size: 30px;
|
||||
}
|
||||
.upload-div {
|
||||
position: fixed;
|
||||
bottom: 10rem;
|
||||
width: 100vw;
|
||||
text-align: center;
|
||||
}
|
||||
.upload-div a {
|
||||
color: var(--text-secondary);
|
||||
background-color: var(--bg-primary);
|
||||
text-decoration: none;
|
||||
font-size: 30px;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* Upload page */
|
||||
main.upload {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* Queue page */
|
||||
main.queue {
|
||||
height: 85%;
|
||||
}
|
||||
.queue-list-div {
|
||||
height: 75vh;
|
||||
}
|
||||
.queue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
list-style-type: decimal;
|
||||
color: var(--text-primary);
|
||||
font-size: 20px;
|
||||
}
|
||||
ul.queue-list li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
width: 50%;
|
||||
}
|
||||
.queue-list a {
|
||||
margin-left: 30px;
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* Track page */
|
||||
.track-info {
|
||||
margin-top: 8rem;
|
||||
width: 98vw;
|
||||
}
|
||||
.track-info img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* Color-picker div */
|
||||
#picker {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
#picker .IroColorPicker {
|
||||
margin: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
div#rgbw {
|
||||
height: 100vw;
|
||||
|
||||
}
|
||||
div#rgbw ul {
|
||||
padding-inline-start: 0;
|
||||
height: 95%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
list-style-type: none;
|
||||
align-content: space-between;
|
||||
}
|
||||
div#rgbw li {
|
||||
text-align: start;
|
||||
margin: 0;
|
||||
width: 40%;
|
||||
}
|
||||
div#rgbw li.right {
|
||||
text-align: end;
|
||||
padding-inline-end: 5px;
|
||||
}
|
||||
.dot {
|
||||
height: 5rem;
|
||||
width: 5rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
}
|
||||
.dot p {
|
||||
font-size: 35px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
.dot.red {
|
||||
background-color: red;
|
||||
}
|
||||
.dot.green {
|
||||
background-color: green;
|
||||
}
|
||||
.dot.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.dot.white {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
/* Settings page */
|
||||
.settings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
list-style-type: none;
|
||||
}
|
||||
.settings-list li {
|
||||
margin: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.btn {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 50%;
|
||||
font-size: 40px;
|
||||
padding: 20px 50px;
|
||||
}
|
||||
.settings-list a {
|
||||
color: var(--text-secondary);
|
||||
font-size: 30px;
|
||||
}
|
||||
#speedSlider {
|
||||
width: 80vw;
|
||||
}
|
||||
#ledSlider {
|
||||
width: 80vw;
|
||||
}
|
||||
#ledIntensitySlider {
|
||||
width: 80vw;
|
||||
}
|
||||
#ledSaturationSlider {
|
||||
width: 80vw;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,35 @@
|
||||
var body = document.body;
|
||||
body.style.margin = 0;
|
||||
|
||||
var div = document.getElementById('canvas-div');
|
||||
var canvas = document.getElementById('chart');
|
||||
canvas.height = div.offsetHeight;
|
||||
canvas.width = div.offsetWidth;
|
||||
|
||||
var x_center = div.offsetWidth/2
|
||||
var y_center = div.offsetWidth/2
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
|
||||
// sand background
|
||||
var sand = new Image();
|
||||
sand.src = "static/images/sand.jpg"
|
||||
var pat = ctx.createPattern(sand, "repeat")
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = pat;
|
||||
ctx.arc(x_center, y_center, div.offsetWidth/2-5, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.lineWidth = 5;
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
|
||||
|
||||
// sand leds
|
||||
var leds = ctx.createRadialGradient(x_center, y_center, 0, x_center, y_center, div.offsetWidth/2+50)
|
||||
leds.addColorStop(0, "rgba(255,0,255,0.1)")
|
||||
leds.addColorStop(1, "rgba(255,0,255,0.3)")
|
||||
ctx.fillStyle = leds;
|
||||
ctx.fill();
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
var div = document.getElementById('picker')
|
||||
var red = document.getElementById('red')
|
||||
var green = document.getElementById('green')
|
||||
var blue = document.getElementById('blue')
|
||||
var white = document.getElementById('white')
|
||||
var red_value = red.innerHTML
|
||||
var green_value = green.innerHTML
|
||||
var blue_value = blue.innerHTML
|
||||
var white_value = white.innerHTML
|
||||
|
||||
var colorPicker = new iro.ColorPicker('#picker', {
|
||||
// Set the size of the color picker
|
||||
width: div.offsetWidth-40,
|
||||
// Set the initial color to pure red
|
||||
color: {r: red_value, g: green_value, b: blue_value},
|
||||
sliderSize: 100,
|
||||
handleRadius: 35,
|
||||
margin: 20,
|
||||
wheelLightness: false
|
||||
});
|
||||
|
||||
|
||||
var whitePicker = new iro.ColorPicker('#picker', {
|
||||
width: div.offsetWidth-40,
|
||||
color: {r: white_value, g: white_value, b: white_value},
|
||||
sliderSize: 100,
|
||||
handleRadius: 35,
|
||||
value: 0,
|
||||
layout: [
|
||||
{
|
||||
component: iro.ui.Slider,
|
||||
options: {
|
||||
sliderType: 'value'
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
colorPicker.on('input:end', function(color) {
|
||||
// Get RGB values
|
||||
var R = color.rgb['r'];
|
||||
var G = color.rgb['g'];
|
||||
var B = color.rgb['b'];
|
||||
|
||||
// Write value to div
|
||||
red.innerHTML = R;
|
||||
green.innerHTML = G;
|
||||
blue.innerHTML = B;
|
||||
|
||||
// POST values without refreshing
|
||||
const formData = new FormData();
|
||||
formData.append('r', R);
|
||||
formData.append('g', G);
|
||||
formData.append('b', B);
|
||||
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
|
||||
console.log(formData);
|
||||
fetch('/color/', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
whitePicker.on('input:end', function(color) {
|
||||
// Get white value
|
||||
var W = parseInt(color.value * 2.555);
|
||||
|
||||
// Write value to div
|
||||
white.innerHTML = W;
|
||||
|
||||
// POST values without refreshing
|
||||
const formData = new FormData();
|
||||
formData.append('w', W);
|
||||
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
|
||||
console.log(formData);
|
||||
fetch('/color/', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
Vendored
+7
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href ="{% static 'css/style.css' %}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% block content %}{% endblock content %}
|
||||
|
||||
|
||||
|
||||
<nav class="nav">
|
||||
<ul class="nav-list">
|
||||
<li>
|
||||
<a href="{% url 'sandtable:index' %}">
|
||||
<span class="icon"><ion-icon name="infinite-outline"></ion-icon></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'sandtable:tracks' %}">
|
||||
<span class="icon"><ion-icon name="list-outline"></ion-icon></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'sandtable:color' %}">
|
||||
<span class="icon"><ion-icon name="color-filter-outline"></ion-icon></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'sandtable:settings' %}">
|
||||
<span class="icon"><ion-icon name="settings-outline"></ion-icon></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="track-list-div">
|
||||
<ul class="settings-list">
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="+1" name="+1">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="-1" name="-1">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="+10" name="+10">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="-10" name="-10">
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='colorPicker'>
|
||||
<div class="header center">
|
||||
<a>Color picker</a>
|
||||
</div>
|
||||
<div class="track-list-div color-div">
|
||||
<div id="picker"></div>
|
||||
<div id="rgbw">
|
||||
<ul>
|
||||
<li>
|
||||
<a>
|
||||
<span class="dot red">R
|
||||
<p id="red">{{ colors.r }}</p>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="right">
|
||||
<a>
|
||||
<span class="dot green">G
|
||||
<p id="green">{{ colors.g }}</p>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<span class="dot blue">B
|
||||
<p id="blue">{{ colors.b }}</p>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="right">
|
||||
<a>
|
||||
<span class="dot white">W
|
||||
<p id="white">{{ colors.w }}</p>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="{% static 'js/iro.min.js' %}"></script>
|
||||
<script src="{% static 'js/color.js' %}"></script>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='index'>
|
||||
<div id="canvas-div">
|
||||
<canvas id="chart"></canvas>
|
||||
<script src="{% static 'js/chart.js' %}"></script>
|
||||
</div>
|
||||
|
||||
<div class='track-playing'>
|
||||
<p>{{ track_name }}</p>
|
||||
</div>
|
||||
|
||||
<div class='media'>
|
||||
<form action="#" method="get" id="eraseForm">
|
||||
<a id='erase'>
|
||||
<input type="hidden" name="eraseButton" value="true">
|
||||
<span class="icon"><ion-icon name="trash-outline"></ion-icon></span>
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<form action="#" method="get" id="playForm">
|
||||
<input type="hidden" name="playButton" value="true">
|
||||
<a id='play'>
|
||||
{% if playing %}
|
||||
<span class="icon"><ion-icon name="pause-outline"></ion-icon></span>
|
||||
{% else %}
|
||||
<span class="icon"><ion-icon name="play-outline"></ion-icon></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<form action="#" method="get" id="nextForm">
|
||||
<input type="hidden" name="nextButton" value="true">
|
||||
<a id='next' href="#">
|
||||
<span class="icon"><ion-icon name="play-forward-outline"></ion-icon></span>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var play_button = document.getElementById("play");
|
||||
var erase_button = document.getElementById("erase");
|
||||
var next_button = document.getElementById("next");
|
||||
|
||||
play_button.onpointerup = function() {
|
||||
document.getElementById("playForm").submit();
|
||||
}
|
||||
erase_button.onpointerup = function() {
|
||||
document.getElementById("eraseForm").submit();
|
||||
}
|
||||
next_button.onpointerup = function() {
|
||||
document.getElementById("nextForm").submit();
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='queue'>
|
||||
<div class="header">
|
||||
<ul class="header-list">
|
||||
<li>
|
||||
<a href="{% url 'sandtable:tracks' %}">Tracks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'sandtable:queue' %}">Queue</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="track-list-div">
|
||||
<ul class="queue-list">
|
||||
{% for track in queue %}
|
||||
<li>
|
||||
<a>{{ track.file }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class='empty'><a>Queue is empty.</a></div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="upload-div">
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Clear queue" name="clearQueue">
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,128 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='settings'>
|
||||
<div class="header center">
|
||||
<a>Settings</a>
|
||||
</div>
|
||||
<div class="track-list-div">
|
||||
<ul class="settings-list">
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Led fade" name="fadeButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Home" name="homeButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Calibrate" name="calibrateButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Update coords" name="updateButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Stop motors" name="stopButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Power off" name="powerButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Reset core" name="resetButton">
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a>Motor speed:</a>
|
||||
<form action="#" method="get" id="motorSliderForm">
|
||||
<input type="range" id="speedSlider" min="1" max="10" value={{ motorSpeed }} name="motorSpeedSlider">
|
||||
<a id="speedSliderValue"></a>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a>Led speed:</a>
|
||||
<form action="#" method="get" id="ledSliderForm">
|
||||
<input type="range" id="ledSlider" min="1" max="200" value={{ ledSpeed }} name="ledSpeedSlider">
|
||||
<a id="ledSliderValue"></a>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a>Led intensity:</a>
|
||||
<form action="#" method="get" id="ledIntensitySliderForm">
|
||||
<input type="range" id="ledIntensitySlider" min="0" max="100" value={{ ledIntensity }} name="ledIntensitySlider">
|
||||
<a id="ledIntensitySliderValue"></a>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a>Led saturation:</a>
|
||||
<form action="#" method="get" id="ledSaturationSliderForm">
|
||||
<input type="range" id="ledSaturationSlider" min="0" max="100" value={{ ledSaturation }} name="ledSaturationSlider">
|
||||
<a id="ledSaturationSliderValue"></a>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var speedSlider = document.getElementById("speedSlider");
|
||||
var speedValue = document.getElementById("speedSliderValue");
|
||||
speedValue.innerHTML = speedSlider.value;
|
||||
|
||||
speedSlider.oninput = function() {
|
||||
speedValue.innerHTML = this.value;
|
||||
}
|
||||
|
||||
speedSlider.onpointerup = function() {
|
||||
document.getElementById("motorSliderForm").submit();
|
||||
}
|
||||
|
||||
var ledSlider = document.getElementById("ledSlider");
|
||||
var ledValue = document.getElementById("ledSliderValue");
|
||||
ledValue.innerHTML = ledSlider.value;
|
||||
|
||||
ledSlider.oninput = function() {
|
||||
ledValue.innerHTML = this.value;
|
||||
}
|
||||
|
||||
ledSlider.onpointerup = function() {
|
||||
document.getElementById("ledSliderForm").submit();
|
||||
}
|
||||
|
||||
var ledIntensitySlider = document.getElementById("ledIntensitySlider");
|
||||
var ledIntensityValue = document.getElementById("ledIntensitySliderValue");
|
||||
ledIntensityValue.innerHTML = ledIntensitySlider.value;
|
||||
|
||||
ledIntensitySlider.oninput = function() {
|
||||
ledIntensityValue.innerHTML = this.value;
|
||||
}
|
||||
|
||||
ledIntensitySlider.onpointerup = function() {
|
||||
document.getElementById("ledIntensitySliderForm").submit();
|
||||
}
|
||||
|
||||
var ledSaturationSlider = document.getElementById("ledSaturationSlider");
|
||||
var ledSaturationValue = document.getElementById("ledSaturationSliderValue");
|
||||
ledSaturationValue.innerHTML = ledSaturationSlider.value;
|
||||
|
||||
ledSaturationSlider.oninput = function() {
|
||||
ledSaturationValue.innerHTML = this.value;
|
||||
}
|
||||
|
||||
ledSaturationSlider.onpointerup = function() {
|
||||
document.getElementById("ledSaturationSliderForm").submit();
|
||||
}
|
||||
|
||||
</script>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="header center">
|
||||
<a>{{ track.name }}</a>
|
||||
<a style="font-size: 20px;">Length: {{ track.track_length }}</a>
|
||||
</div>
|
||||
<div class="track-info">
|
||||
<form action="#" method="get">
|
||||
<input type="submit" class="btn" value="Delete track" name="deleteTrack">
|
||||
</form>
|
||||
<img src="{{ track.pic.url }}">
|
||||
</div>
|
||||
<div class="center">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.field.as_hidden }}
|
||||
<button class="btn" type="submit">Add to queue</button>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='tracks'>
|
||||
<div class="header">
|
||||
<ul class="header-list">
|
||||
<li>
|
||||
<a href="{% url 'sandtable:tracks' %}">Tracks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'sandtable:queue' %}">Queue</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="track-list-div">
|
||||
<ul class="track-list">
|
||||
{% for track in tracks %}
|
||||
<li>
|
||||
<a href="{% url 'sandtable:track' track.id %}">
|
||||
<img class="list-pic" src="{{ track.pic.url }}">
|
||||
<a class="track-name" href="{% url 'sandtable:track' track.id %}">{{ track.name }}</a>
|
||||
</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class='empty'><a>No tracks have been added yet.</a></div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="upload-div">
|
||||
<a href="{% url 'sandtable:upload' %}">Upload</a>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "sandtable/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main class='upload'>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
|
||||
<p><a href="{% url 'sandtable:tracks' %}">Return to home</a></p>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,16 @@
|
||||
from unicodedata import name
|
||||
from django.urls import path, include
|
||||
from .views import *
|
||||
from .forms import *
|
||||
|
||||
app_name = 'sandtable'
|
||||
urlpatterns = [
|
||||
path('', index, name='index'),
|
||||
path('tracks/', tracks, name='tracks'),
|
||||
path('queue/', queue, name='queue'),
|
||||
path('settings/', settings, name='settings'),
|
||||
path('calibrate/', calibrate, name='calibrate'),
|
||||
path(r'color/', color, name='color'),
|
||||
path('upload/', uploadTracks, name='upload'),
|
||||
path('tracks/<int:track_id>', track, name='track')
|
||||
]
|
||||
@@ -0,0 +1,436 @@
|
||||
import colorsys
|
||||
from xml.dom import ValidationErr
|
||||
from django.shortcuts import render, redirect
|
||||
from .models import *
|
||||
from .forms import *
|
||||
import thr2png, os, psutil, writeSerial, playControl
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# LED handling
|
||||
#
|
||||
# The device no longer stores an LED track itself - every speed/intensity/
|
||||
# saturation/color change has to rebuild the full track and resend it via
|
||||
# writeSerial.setLedTrack(). What mode is "currently active" is tracked in
|
||||
# the RGBW row (id=1) itself via led_track:
|
||||
#
|
||||
# LED_TRACK_SOLID (0) - use the stored r/g/b/w directly, as a one-color track
|
||||
# LED_TRACK_FADE (5) - cycle through FADE_COLORS (matches what fadeButton
|
||||
# used to trigger on the old firmware)
|
||||
#
|
||||
# I only wired up these two because they're the only two entry points that
|
||||
# exist in this file (color() and the fadeButton branch below). If there
|
||||
# were other led_track presets (1-4) driving different sequences on the old
|
||||
# firmware, tell me what they were and I'll add them.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
LED_TRACK_SOLID = 0
|
||||
LED_TRACK_FADE = 5
|
||||
|
||||
FADE_COLORS = [
|
||||
(255, 0, 0, 0), # Red
|
||||
(255, 255, 0, 0), # Yellow
|
||||
(0, 255, 0, 0), # Green
|
||||
(0, 255, 255, 0), # Cyan
|
||||
(0, 0, 255, 0), # Blue
|
||||
]
|
||||
|
||||
|
||||
def _scale_color(color, intensity_pct, saturation_pct=None):
|
||||
"""Apply intensity (0-100, brightness) and, for hued preset colors,
|
||||
saturation (0-100) to an (r, g, b, w) tuple. The white channel only
|
||||
gets intensity applied - there's no meaningful "saturation" of white."""
|
||||
|
||||
# Defensive: Django doesn't coerce a model field's type on a plain
|
||||
# attribute assignment (only on a DB round trip), so values coming
|
||||
# straight from request.POST/GET can still be strings here - cast
|
||||
# explicitly rather than letting the arithmetic below blow up. Also
|
||||
# guard against None, since the color-picker only ever sets r/g/b OR w
|
||||
# at once, so the other channel(s) can be unset on a fresh row.
|
||||
r, g, b, w = (int(c) if c is not None else 0 for c in color)
|
||||
|
||||
if saturation_pct is not None:
|
||||
h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
|
||||
s *= max(0.0, min(saturation_pct, 100.0)) / 100.0
|
||||
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
||||
r, g, b = r * 255, g * 255, b * 255
|
||||
|
||||
scale = max(0.0, min(intensity_pct, 100.0)) / 100.0
|
||||
return (
|
||||
round(r * scale),
|
||||
round(g * scale),
|
||||
round(b * scale),
|
||||
round(w * scale),
|
||||
)
|
||||
|
||||
|
||||
def _sendCurrentLed(led_data):
|
||||
"""Rebuild whatever LED mode is currently active from its stored
|
||||
settings and resend the full track."""
|
||||
|
||||
speed = int(led_data.led_speed or writeSerial.MIN_TRACK_SPEED)
|
||||
intensity = float(led_data.led_intensity) if led_data.led_intensity is not None else 100.0
|
||||
saturation = float(led_data.led_saturation) if led_data.led_saturation is not None else 100.0
|
||||
|
||||
if led_data.led_track == LED_TRACK_FADE:
|
||||
colors = [_scale_color(c, intensity, saturation) for c in FADE_COLORS]
|
||||
else:
|
||||
# Solid color: saturation doesn't really mean anything for an
|
||||
# explicitly hand-picked RGB value, so only intensity (brightness)
|
||||
# is applied here. Speed is also irrelevant for a single color.
|
||||
colors = [_scale_color((led_data.r, led_data.g, led_data.b, led_data.w), intensity)]
|
||||
|
||||
writeSerial.setLedTrack(colors, speed, wait_ack=False)
|
||||
|
||||
|
||||
def index(request):
|
||||
status = playControl.get_status()
|
||||
playing = status['status'] in (playControl.STATUS_PLAYING, playControl.STATUS_PAUSED)
|
||||
|
||||
queue = Queue.objects.all()
|
||||
track_name = ""
|
||||
if len(queue) > 0:
|
||||
track = queue[0].file
|
||||
track_name = track.split('/')[-1].replace(".thr", '')
|
||||
|
||||
# Erase button
|
||||
if(request.GET.get('eraseButton')):
|
||||
playControl.stop_player()
|
||||
return redirect('sandtable:index')
|
||||
|
||||
# Button to start playing
|
||||
if(request.GET.get('playButton')):
|
||||
status = playControl.get_status()
|
||||
|
||||
if status['status'] == playControl.STATUS_IDLE:
|
||||
os.system("/bin/python /home/pi/SandTable/PlayQueue.py &")
|
||||
elif status['status'] == playControl.STATUS_PLAYING:
|
||||
playControl.set_command(playControl.COMMAND_PAUSE)
|
||||
elif status['status'] == playControl.STATUS_PAUSED:
|
||||
playControl.set_command(playControl.COMMAND_PLAY)
|
||||
|
||||
return redirect('sandtable:index')
|
||||
|
||||
# Next button
|
||||
if(request.GET.get('nextButton')):
|
||||
if playControl.get_status()['status'] == playControl.STATUS_IDLE:
|
||||
print("Not running")
|
||||
else:
|
||||
playControl.set_command(playControl.COMMAND_NEXT)
|
||||
|
||||
print("next")
|
||||
return redirect('sandtable:index')
|
||||
|
||||
context = { 'playing': playing, 'track_name': track_name }
|
||||
return render(request, 'sandtable/index.html', context)
|
||||
|
||||
|
||||
def tracks(request):
|
||||
tracks = Tracks.objects.order_by('uploaded_at')
|
||||
context = { 'tracks': tracks }
|
||||
|
||||
return render(request, 'sandtable/tracks.html', context)
|
||||
|
||||
|
||||
def track(request, track_id):
|
||||
track = Tracks.objects.get(id=track_id)
|
||||
|
||||
# Button to delete track
|
||||
if(request.GET.get('deleteTrack')):
|
||||
track.delete()
|
||||
return redirect('sandtable:tracks')
|
||||
|
||||
# Add track to queue
|
||||
elif request.method == 'POST':
|
||||
form = AddToQueueForm(request.POST)
|
||||
if form.is_valid():
|
||||
new_queue_track = form.save(commit=False)
|
||||
new_queue_track.file = track.file
|
||||
new_queue_track.track_length = track.track_length
|
||||
new_queue_track.pic = track.pic
|
||||
new_queue_track.save()
|
||||
|
||||
# Start playing
|
||||
if playControl.get_status()['status'] == playControl.STATUS_IDLE:
|
||||
os.system("/bin/python /home/pi/SandTable/PlayQueue.py &")
|
||||
|
||||
return redirect('sandtable:tracks')
|
||||
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
|
||||
context = { 'track': track, 'form': form }
|
||||
|
||||
return render(request, 'sandtable/track.html', context)
|
||||
|
||||
|
||||
def queue(request):
|
||||
queue = Queue.objects.all()
|
||||
context = { 'queue': queue }
|
||||
|
||||
# If clearQueue button is pressed then delete all entries in queue table
|
||||
if(request.GET.get('clearQueue')):
|
||||
Queue.objects.all().delete()
|
||||
return redirect('sandtable:queue')
|
||||
|
||||
return render(request, 'sandtable/queue.html', context)
|
||||
|
||||
|
||||
def uploadTracks(request):
|
||||
if request.method == 'POST':
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
new_track = form.save(commit=False)
|
||||
|
||||
# Get file name
|
||||
file_name = request.FILES['file'].name
|
||||
|
||||
# If file exists then raise error
|
||||
if os.path.isfile('/home/pi/SandTable/media/tracks/' + file_name):
|
||||
raise ValidationErr('File already exists')
|
||||
|
||||
# If file is not .thr then raise error
|
||||
if '.thr' not in file_name:
|
||||
raise ValidationErr('Not a thr file')
|
||||
|
||||
name = file_name.split('.')
|
||||
new_track.name = name[0]
|
||||
new_track.save()
|
||||
# Generate image from .thr and return track length
|
||||
new_track.track_length = thr2png.drawFile('/home/pi/SandTable/media/tracks/' + file_name)
|
||||
new_track.pic = 'tracks/' + name[0] + '.png'
|
||||
new_track.save()
|
||||
|
||||
return redirect('sandtable:tracks')
|
||||
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
|
||||
context = { 'form': form }
|
||||
|
||||
return render(request, 'sandtable/uploadTracks.html', context)
|
||||
|
||||
|
||||
def settings(request):
|
||||
settings_data = Settings.objects.get(id=0)
|
||||
motor_speed = settings_data.motor_speed
|
||||
|
||||
led_data = RGBW.objects.get(id=1)
|
||||
led_speed = led_data.led_speed
|
||||
led_intensity = led_data.led_intensity
|
||||
led_saturation = led_data.led_saturation
|
||||
|
||||
# Button to home motors
|
||||
if(request.GET.get('homeButton')):
|
||||
playControl.stop_player()
|
||||
writeSerial.home(wait_ack=False)
|
||||
# No more r/theta reset here - the firmware tracks position itself
|
||||
# now, and calibration offsets are stored firmware-side too (see
|
||||
# COMMAND_SET_OFFSET), so there's nothing left to reapply after a
|
||||
# home like the old code used to do with COMMAND_M2_STEP.
|
||||
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Button to calibrate
|
||||
if(request.GET.get('calibrateButton')):
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
# Button to stop motors
|
||||
elif(request.GET.get('stopButton')):
|
||||
playControl.stop_player()
|
||||
# ASSUMPTION: mapped the old immediate-stop button to
|
||||
# COMMAND_DISABLE_MOTORS since there's no direct "stop" in the new
|
||||
# enum. This likely de-energizes the steppers rather than just
|
||||
# pausing them, so you'll probably need to re-home before playing
|
||||
# again - confirm this is the behavior you want.
|
||||
writeSerial.disableMotors(wait_ack=False)
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Button to power down the pi
|
||||
elif(request.GET.get('powerButton')):
|
||||
playControl.stop_player()
|
||||
os.system("sudo shutdown -h")
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Button to reset core 1
|
||||
elif(request.GET.get('resetButton')):
|
||||
# TODO: no equivalent to the old COMMAND_RESET in the new
|
||||
# commands_e enum. If resetting core 1 is still needed, this needs
|
||||
# a new firmware command.
|
||||
print("resetButton: not implemented under the new protocol yet")
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Button to do led fade
|
||||
elif(request.GET.get('fadeButton')):
|
||||
led_data.led_track = LED_TRACK_FADE
|
||||
led_data.save()
|
||||
_sendCurrentLed(led_data)
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Motor speed slider
|
||||
elif(request.GET.get('motorSpeedSlider')):
|
||||
value = request.GET.get('motorSpeedSlider')
|
||||
settings_data.motor_speed = int(value)
|
||||
settings_data.save()
|
||||
writeSerial.setMotorSpeed(int(value), wait_ack=False)
|
||||
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Led speed slider
|
||||
elif(request.GET.get('ledSpeedSlider')):
|
||||
value = request.GET.get('ledSpeedSlider')
|
||||
led_data.led_speed = int(value)
|
||||
led_data.save()
|
||||
_sendCurrentLed(led_data)
|
||||
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Led intensity slider
|
||||
elif(request.GET.get('ledIntensitySlider')):
|
||||
value = request.GET.get('ledIntensitySlider')
|
||||
led_data.led_intensity = int(value)
|
||||
led_data.save()
|
||||
_sendCurrentLed(led_data)
|
||||
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
# Led saturation slider
|
||||
elif(request.GET.get('ledSaturationSlider')):
|
||||
value = request.GET.get('ledSaturationSlider')
|
||||
led_data.led_saturation = int(value)
|
||||
led_data.save()
|
||||
_sendCurrentLed(led_data)
|
||||
|
||||
return redirect('sandtable:settings')
|
||||
|
||||
context = { 'motorSpeed': motor_speed, 'ledSpeed': led_speed, 'ledIntensity': led_intensity, 'ledSaturation': led_saturation }
|
||||
|
||||
return render(request, 'sandtable/settings.html', context)
|
||||
|
||||
def calibrate(request):
|
||||
settings_data = Settings.objects.get(id=0)
|
||||
|
||||
# Calibration is now handled firmware-side (COMMAND_SET_OFFSET adds to
|
||||
# a stored per-motor step offset, COMMAND_RESET_OFFSET zeros it out).
|
||||
# These buttons only ever adjusted motor 2 in the old code (M2_STEP),
|
||||
# so that's preserved here - m1's delta is always 0. `settings_data.
|
||||
# calibrate` is kept purely as a local display counter mirroring what's
|
||||
# been sent; the firmware is the actual source of truth for the offset
|
||||
# itself.
|
||||
|
||||
if(request.GET.get('+1')):
|
||||
writeSerial.setOffset(0, 1, wait_ack=False)
|
||||
settings_data.calibrate += 1
|
||||
settings_data.save()
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
if(request.GET.get('-1')):
|
||||
writeSerial.setOffset(0, -1, wait_ack=False)
|
||||
settings_data.calibrate -= 1
|
||||
settings_data.save()
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
if(request.GET.get('+10')):
|
||||
writeSerial.setOffset(0, 10, wait_ack=False)
|
||||
settings_data.calibrate += 10
|
||||
settings_data.save()
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
if(request.GET.get('-10')):
|
||||
writeSerial.setOffset(0, -10, wait_ack=False)
|
||||
settings_data.calibrate -= 10
|
||||
settings_data.save()
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
# New: full reset of the firmware's calibration offset. There wasn't a
|
||||
# button for this before (COMMAND_RESET_OFFSET didn't exist yet) - I've
|
||||
# added the handler so it's ready, but you'll need to add the actual
|
||||
# button/link in calibrate.html to trigger it (?resetButton=1).
|
||||
if(request.GET.get('resetButton')):
|
||||
writeSerial.resetOffset(wait_ack=False)
|
||||
settings_data.calibrate = 0
|
||||
settings_data.save()
|
||||
return redirect('sandtable:calibrate')
|
||||
|
||||
return render(request, 'sandtable/calibrate.html')
|
||||
|
||||
@csrf_exempt
|
||||
def color(request):
|
||||
|
||||
# If entry in the table does not exist, then create new with id=1
|
||||
if not RGBW.objects.filter(id=1).exists():
|
||||
if request.method == 'POST':
|
||||
newcolor = RGBW()
|
||||
newcolor.id = 1
|
||||
|
||||
# If POST contains RGB or WHITE
|
||||
if 'r' in request.POST:
|
||||
newcolor.r = int(request.POST.get('r'))
|
||||
newcolor.g = int(request.POST.get('g'))
|
||||
newcolor.b = int(request.POST.get('b'))
|
||||
elif 'w' in request.POST:
|
||||
newcolor.w = int(request.POST.get('w'))
|
||||
|
||||
# Don't know why I thought that I needed this
|
||||
newcolor.changed = True
|
||||
newcolor.led_track = LED_TRACK_SOLID
|
||||
newcolor.save()
|
||||
|
||||
_sendCurrentLed(newcolor)
|
||||
|
||||
return HttpResponse('')
|
||||
|
||||
else:
|
||||
context = { 'colors': {'r':0, 'g':0, 'b':0, 'w':0} }
|
||||
return render(request, 'sandtable/colorPicker.html', context)
|
||||
|
||||
else:
|
||||
colors = RGBW.objects.get(id=1)
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
# If POST contains RGB or WHITE
|
||||
if 'r' in request.POST:
|
||||
colors.r = int(request.POST.get('r'))
|
||||
colors.g = int(request.POST.get('g'))
|
||||
colors.b = int(request.POST.get('b'))
|
||||
elif 'w' in request.POST:
|
||||
colors.w = int(request.POST.get('w'))
|
||||
|
||||
# Don't know why I thought that I needed this
|
||||
colors.changed = True
|
||||
colors.led_track = LED_TRACK_SOLID
|
||||
colors.save()
|
||||
|
||||
_sendCurrentLed(colors)
|
||||
|
||||
return HttpResponse('')
|
||||
|
||||
else:
|
||||
context = { 'colors': colors }
|
||||
return render(request, 'sandtable/colorPicker.html', context)
|
||||
|
||||
|
||||
# Find if process is running and killit if needed
|
||||
# NOTE: no longer used by the play/pause/stop/skip buttons above (they use
|
||||
# playControl instead) - left in place in case anything else in the project
|
||||
# still calls it.
|
||||
def checkProcesses(name, kill=False):
|
||||
for proc in psutil.process_iter(attrs=["pid", "name", "cmdline"]):
|
||||
try:
|
||||
# Check if process name contains the given name string.
|
||||
if "python" in proc.name():
|
||||
for cmd in proc.cmdline():
|
||||
if name.lower() in cmd.lower():
|
||||
|
||||
# Kill the process if kill=True
|
||||
if kill:
|
||||
proc.kill()
|
||||
|
||||
return proc.pid
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
pass
|
||||
return False
|
||||
Reference in New Issue
Block a user