Preparing Web Interface for Lamp Matrix Controller
This commit is contained in:
parent
2aaffd8ba1
commit
d79fc79519
333
src/controllerSoftware/templates/index.html
Normal file
333
src/controllerSoftware/templates/index.html
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>RGB Lamp Matrix Control</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.matrix-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 70px);
|
||||||
|
grid-template-rows: repeat(5, 70px);
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.lamp {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 10%;
|
||||||
|
background-color: #000;
|
||||||
|
transition: box-shadow 0.2s, transform 0.1s;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.lamp.on {
|
||||||
|
box-shadow: 0 0 15px currentColor, 0 0 25px currentColor;
|
||||||
|
}
|
||||||
|
.lamp.selected {
|
||||||
|
border: 2px solid #fff;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.region-control {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.region-control button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NEW: Container for the two slider groups */
|
||||||
|
.slider-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px; /* Space between the two panels */
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjusted styling for the region control panel */
|
||||||
|
.control-panel {
|
||||||
|
background-color: #444;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: none;
|
||||||
|
/* Removed absolute positioning */
|
||||||
|
}
|
||||||
|
.slider-group {
|
||||||
|
width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.slider-group input {
|
||||||
|
width: 100%;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.slider-group input::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 0 5px rgba(0,0,0,0.5);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.slider-group input::-webkit-slider-runnable-track {
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
input.red::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #f00); }
|
||||||
|
input.green::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #0f0); }
|
||||||
|
input.blue::-webkit-slider-runnable-track { background: linear-gradient(to right, #000, #00f); }
|
||||||
|
.slider-label {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.center-lamp-control {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #444;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.center-lamp-control h2 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var selectedLamps = [];
|
||||||
|
|
||||||
|
function updateSliders(color) {
|
||||||
|
var rgb = hexToRgb(color);
|
||||||
|
$('#red-slider').val(rgb.r);
|
||||||
|
$('#green-slider').val(rgb.g);
|
||||||
|
$('#blue-slider').val(rgb.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result ? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
} : {r: 0, g: 0, b: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var regionMaps = {
|
||||||
|
'Upper': [
|
||||||
|
{row: 0, col: 0}, {row: 0, col: 1}, {row: 0, col: 2}, {row: 0, col: 3}, {row: 0, col: 4},
|
||||||
|
{row: 1, col: 0}, {row: 1, col: 1}, {row: 1, col: 2}, {row: 1, col: 3}, {row: 1, col: 4},
|
||||||
|
],
|
||||||
|
'Lower': [
|
||||||
|
{row: 3, col: 0}, {row: 3, col: 1}, {row: 3, col: 2}, {row: 3, col: 3}, {row: 3, col: 4},
|
||||||
|
{row: 4, col: 0}, {row: 4, col: 1}, {row: 4, col: 2}, {row: 4, col: 3}, {row: 4, col: 4},
|
||||||
|
],
|
||||||
|
'Left': [
|
||||||
|
{row: 0, col: 0}, {row: 1, col: 0}, {row: 2, col: 0}, {row: 3, col: 0}, {row: 4, col: 0},
|
||||||
|
{row: 0, col: 1}, {row: 1, col: 1}, {row: 2, col: 1}, {row: 3, col: 1}, {row: 4, col: 1},
|
||||||
|
],
|
||||||
|
'Right': [
|
||||||
|
{row: 0, col: 3}, {row: 1, col: 3}, {row: 2, col: 3}, {row: 3, col: 3}, {row: 4, col: 3},
|
||||||
|
{row: 0, col: 4}, {row: 1, col: 4}, {row: 2, col: 4}, {row: 3, col: 4}, {row: 4, col: 4},
|
||||||
|
],
|
||||||
|
'Inner ring': [
|
||||||
|
{row: 1, col: 1}, {row: 1, col: 2}, {row: 1, col: 3},
|
||||||
|
{row: 2, col: 1}, {row: 2, col: 3},
|
||||||
|
{row: 3, col: 1}, {row: 3, col: 2}, {row: 3, col: 3}
|
||||||
|
],
|
||||||
|
'Outer ring': [
|
||||||
|
{row: 0, col: 0}, {row: 0, col: 1}, {row: 0, col: 2}, {row: 0, col: 3}, {row: 0, col: 4},
|
||||||
|
{row: 1, col: 0}, {row: 1, col: 4},
|
||||||
|
{row: 2, col: 0}, {row: 2, col: 4},
|
||||||
|
{row: 3, col: 0}, {row: 3, col: 4},
|
||||||
|
{row: 4, col: 0}, {row: 4, col: 1}, {row: 4, col: 2}, {row: 4, col: 3}, {row: 4, col: 4},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
$('.region-button').on('click', function() {
|
||||||
|
var region = $(this).data('region');
|
||||||
|
selectedLamps = regionMaps[region];
|
||||||
|
|
||||||
|
$('.lamp').removeClass('selected');
|
||||||
|
$('.lamp').not('[data-row="2"][data-col="2"]').css('box-shadow', 'inset 0 0 5px rgba(0,0,0,0.5)');
|
||||||
|
|
||||||
|
selectedLamps.forEach(function(lamp) {
|
||||||
|
$(`.lamp[data-row="${lamp.row}"][data-col="${lamp.col}"]`).addClass('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedLamps.length > 0) {
|
||||||
|
$('.control-panel').show();
|
||||||
|
var firstLamp = selectedLamps[0];
|
||||||
|
var firstLampElement = $(`.lamp[data-row="${firstLamp.row}"][data-col="${firstLamp.col}"]`);
|
||||||
|
var currentColor = firstLampElement.css('background-color');
|
||||||
|
updateSliders(currentColor);
|
||||||
|
} else {
|
||||||
|
$('.control-panel').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.region-slider-group input').on('input', function() {
|
||||||
|
if (selectedLamps.length === 0) return;
|
||||||
|
|
||||||
|
var r = $('#red-slider').val();
|
||||||
|
var g = $('#green-slider').val();
|
||||||
|
var b = $('#blue-slider').val();
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
lamps: selectedLamps,
|
||||||
|
r: r,
|
||||||
|
g: g,
|
||||||
|
b: b
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/set_color',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
var newColor = response.new_color;
|
||||||
|
|
||||||
|
$('.lamp').each(function() {
|
||||||
|
var currentLamp = $(this);
|
||||||
|
var lampRow = currentLamp.data('row');
|
||||||
|
var lampCol = currentLamp.data('col');
|
||||||
|
var isCenterLamp = (lampRow === 2 && lampCol === 2);
|
||||||
|
var isSelected = selectedLamps.some(lamp => lamp.row === lampRow && lamp.col === lampCol);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
currentLamp.css('background-color', newColor);
|
||||||
|
if (newColor === '#000000') {
|
||||||
|
currentLamp.removeClass('on');
|
||||||
|
} else {
|
||||||
|
currentLamp.addClass('on');
|
||||||
|
currentLamp.css('box-shadow', `0 0 15px ${newColor}, 0 0 25px ${newColor}`);
|
||||||
|
}
|
||||||
|
} else if (!isCenterLamp) {
|
||||||
|
currentLamp.css('background-color', '#000000');
|
||||||
|
currentLamp.removeClass('on');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.center-slider-group input').on('input', function() {
|
||||||
|
var r = $('#center-red-slider').val();
|
||||||
|
var g = $('#center-green-slider').val();
|
||||||
|
var b = $('#center-blue-slider').val();
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
lamps: [{row: 2, col: 2}],
|
||||||
|
r: r,
|
||||||
|
g: g,
|
||||||
|
b: b
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/set_color',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
var newColor = response.new_color;
|
||||||
|
var centerLamp = $(`.lamp[data-row="2"][data-col="2"]`);
|
||||||
|
centerLamp.css('background-color', newColor);
|
||||||
|
|
||||||
|
if (newColor === '#000000') {
|
||||||
|
centerLamp.removeClass('on');
|
||||||
|
} else {
|
||||||
|
centerLamp.addClass('on');
|
||||||
|
centerLamp.css('box-shadow', `0 0 15px ${newColor}, 0 0 25px ${newColor}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>RGB Lamp Matrix Control</h1>
|
||||||
|
<div class="region-control">
|
||||||
|
<button class="region-button" data-region="Upper">Upper</button>
|
||||||
|
<button class="region-button" data-region="Lower">Lower</button>
|
||||||
|
<button class="region-button" data-region="Left">Left</button>
|
||||||
|
<button class="region-button" data-region="Right">Right</button>
|
||||||
|
<button class="region-button" data-region="Inner ring">Inner ring</button>
|
||||||
|
<button class="region-button" data-region="Outer ring">Outer ring</button>
|
||||||
|
</div>
|
||||||
|
<div class="matrix-grid">
|
||||||
|
{% for row in range(5) %}
|
||||||
|
{% for col in range(5) %}
|
||||||
|
<div class="lamp" data-row="{{ row }}" data-col="{{ col }}" style="background-color: {{ matrix[row][col] }}; box-shadow: {{ '0 0 15px ' + matrix[row][col] + ', 0 0 25px ' + matrix[row][col] if matrix[row][col] != '#000000' else 'inset 0 0 5px rgba(0,0,0,0.5)' }}"></div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-controls">
|
||||||
|
<div class="center-lamp-control">
|
||||||
|
<h2>Center Lamp</h2>
|
||||||
|
<div class="slider-group center-slider-group">
|
||||||
|
<span class="slider-label">Red</span>
|
||||||
|
<input type="range" id="center-red-slider" class="red" min="0" max="255" value="0">
|
||||||
|
<span class="slider-label">Green</span>
|
||||||
|
<input type="range" id="center-green-slider" class="green" min="0" max="255" value="0">
|
||||||
|
<span class="slider-label">Blue</span>
|
||||||
|
<input type="range" id="center-blue-slider" class="blue" min="0" max="255" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="slider-group region-slider-group">
|
||||||
|
<span class="slider-label">Region Red</span>
|
||||||
|
<input type="range" id="red-slider" class="red" min="0" max="255" value="0">
|
||||||
|
<span class="slider-label">Region Green</span>
|
||||||
|
<input type="range" id="green-slider" class="green" min="0" max="255" value="0">
|
||||||
|
<span class="slider-label">Region Blue</span>
|
||||||
|
<input type="range" id="blue-slider" class="blue" min="0" max="255" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
129
src/controllerSoftware/web.py
Normal file
129
src/controllerSoftware/web.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
|
||||||
|
# ... (paste the two helper functions here if they are not already present) ...
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
def create_spiral_map(n=5):
|
||||||
|
"""
|
||||||
|
Creates a pre-computed spiral mapping for an n x n matrix.
|
||||||
|
The spiral starts from the center and moves outwards.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
n (int): The size of the square matrix.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list of lists: A 2D list where each element is the spiraling address.
|
||||||
|
"""
|
||||||
|
if n % 2 == 0:
|
||||||
|
raise ValueError("Matrix size must be odd for a unique center point.")
|
||||||
|
|
||||||
|
spiral_map = [[0] * n for _ in range(n)]
|
||||||
|
|
||||||
|
# Starting position and index
|
||||||
|
r, c = n // 2, n // 2
|
||||||
|
address = 0
|
||||||
|
spiral_map[r][c] = address
|
||||||
|
|
||||||
|
# Directions: Right, Down, Left, Up
|
||||||
|
dr = [0, 1, 0, -1]
|
||||||
|
dc = [1, 0, -1, 0]
|
||||||
|
direction = 0
|
||||||
|
|
||||||
|
segment_length = 1
|
||||||
|
steps = 0
|
||||||
|
|
||||||
|
while address < n * n - 1:
|
||||||
|
for _ in range(segment_length):
|
||||||
|
address += 1
|
||||||
|
r += dr[direction]
|
||||||
|
c += dc[direction]
|
||||||
|
if 0 <= r < n and 0 <= c < n:
|
||||||
|
spiral_map[r][c] = address
|
||||||
|
|
||||||
|
direction = (direction + 1) % 4
|
||||||
|
steps += 1
|
||||||
|
|
||||||
|
# Increase segment length after every two turns
|
||||||
|
if steps % 2 == 0:
|
||||||
|
segment_length += 1
|
||||||
|
|
||||||
|
return spiral_map
|
||||||
|
|
||||||
|
def get_spiral_address(row, col, spiral_map):
|
||||||
|
"""
|
||||||
|
Converts a standard (row, col) address to a spiraling address
|
||||||
|
using a pre-computed map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
row (int): The row index (0-indexed).
|
||||||
|
col (int): The column index (0-indexed).
|
||||||
|
spiral_map (list of lists): The pre-computed spiral map.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The spiraling address, or -1 if the coordinates are out of bounds.
|
||||||
|
"""
|
||||||
|
n = len(spiral_map)
|
||||||
|
if 0 <= row < n and 0 <= col < n:
|
||||||
|
return spiral_map[row][col]
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# Pre-compute the spiral map for a 5x5 matrix when the app starts
|
||||||
|
SPIRAL_MAP_5x5 = create_spiral_map(5)
|
||||||
|
|
||||||
|
# Initialize a 5x5 matrix with all lamps off (black color code)
|
||||||
|
lamp_matrix = [['#000000' for _ in range(5)] for _ in range(5)]
|
||||||
|
|
||||||
|
# Define a helper function to convert hex to RGB for template use
|
||||||
|
def hex_to_rgb(hex_color):
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
return {
|
||||||
|
'r': int(hex_color[0:2], 16),
|
||||||
|
'g': int(hex_color[2:4], 16),
|
||||||
|
'b': int(hex_color[4:6], 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.jinja_env.globals.update(hex_to_rgb=hex_to_rgb)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""
|
||||||
|
Renders the main control interface for the RGB lamp matrix.
|
||||||
|
"""
|
||||||
|
return render_template('index.html', matrix=lamp_matrix)
|
||||||
|
|
||||||
|
@app.route('/set_color', methods=['POST'])
|
||||||
|
def set_color():
|
||||||
|
"""
|
||||||
|
Sets the color for multiple lamps and turns all others black.
|
||||||
|
"""
|
||||||
|
data = request.get_json()
|
||||||
|
lamps_to_update = data.get('lamps', [])
|
||||||
|
r = data.get('r')
|
||||||
|
g = data.get('g')
|
||||||
|
b = data.get('b')
|
||||||
|
|
||||||
|
try:
|
||||||
|
r, g, b = int(r), int(g), int(b)
|
||||||
|
new_color = '#{:02x}{:02x}{:02x}'.format(r, g, b)
|
||||||
|
|
||||||
|
# First, turn all lamps black
|
||||||
|
for row in range(5):
|
||||||
|
for col in range(5):
|
||||||
|
lamp_matrix[row][col] = '#000000'
|
||||||
|
|
||||||
|
# Then, apply the new color to the selected lamps
|
||||||
|
for lamp in lamps_to_update:
|
||||||
|
row = lamp['row']
|
||||||
|
col = lamp['col']
|
||||||
|
if 0 <= row < 5 and 0 <= col < 5:
|
||||||
|
lamp_matrix[row][col] = new_color
|
||||||
|
|
||||||
|
return jsonify(success=True, new_color=new_color)
|
||||||
|
|
||||||
|
except (ValueError, TypeError, IndexError):
|
||||||
|
return jsonify(success=False, message="Invalid data received")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
Loading…
Reference in New Issue
Block a user