152 lines
6.1 KiB
Python
152 lines
6.1 KiB
Python
from flask import Flask, request, jsonify, Response
|
|
import threading
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Shared state — written by Flask thread, read by main loop
|
|
state = {
|
|
'brightness': 100,
|
|
'program_top_text_enabled': True,
|
|
'program_bottom_text_enabled': True,
|
|
'program_vehicles_enabled': True,
|
|
}
|
|
_lock = threading.Lock()
|
|
|
|
INDEX_HTML = """<!DOCTYPE HTML><html>
|
|
<head>
|
|
<title>LED Panel Control</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
|
|
h1 { text-align: center; margin-bottom: 30px; font-size: 1.5em; }
|
|
.container { max-width: 400px; margin: 0 auto; }
|
|
.card { background: #16213e; border-radius: 12px; padding: 16px 20px; margin-bottom: 12px;
|
|
display: flex; align-items: center; justify-content: space-between; }
|
|
.card-label { font-size: 1em; }
|
|
.card-label small { display: block; color: #888; font-size: 0.75em; margin-top: 2px; }
|
|
.toggle { position: relative; width: 52px; height: 28px; flex-shrink: 0; }
|
|
.toggle input { opacity: 0; width: 0; height: 0; }
|
|
.toggle .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
|
|
background: #444; border-radius: 28px; transition: 0.3s; }
|
|
.toggle .slider:before { content: ""; position: absolute; height: 22px; width: 22px;
|
|
left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: 0.3s; }
|
|
.toggle input:checked + .slider { background: #4CAF50; }
|
|
.toggle input:checked + .slider:before { transform: translateX(24px); }
|
|
.brightness-card { background: #16213e; border-radius: 12px; padding: 16px 20px; margin-bottom: 12px; }
|
|
.brightness-card label { font-size: 1em; display: block; margin-bottom: 8px; }
|
|
.brightness-row { display: flex; align-items: center; gap: 12px; }
|
|
.brightness-row input[type=range] { flex: 1; accent-color: #4CAF50; }
|
|
.brightness-row span { min-width: 32px; text-align: right; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>LED Panel Control</h1>
|
|
<div class="card">
|
|
<div class="card-label">Top Text<small>Witamy w PTI</small></div>
|
|
<label class="toggle"><input type="checkbox" id="top_text" checked onchange="toggle('top_text', this.checked)"><span class="slider"></span></label>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-label">Bottom Text<small>School programs</small></div>
|
|
<label class="toggle"><input type="checkbox" id="bottom_text" checked onchange="toggle('bottom_text', this.checked)"><span class="slider"></span></label>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-label">Vehicles<small>Vehicle interruptions</small></div>
|
|
<label class="toggle"><input type="checkbox" id="vehicles" checked onchange="toggle('vehicles', this.checked)"><span class="slider"></span></label>
|
|
</div>
|
|
<div class="brightness-card">
|
|
<label>Brightness</label>
|
|
<div class="brightness-row">
|
|
<input type="range" id="brightness" min="0" max="100" value="100" oninput="updateBrightnessLabel()" onchange="sendBrightness()">
|
|
<span id="brightnessValue">100</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggle(program, enabled) {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "/toggle-program", true);
|
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
xhr.send("program=" + program + "&enabled=" + (enabled ? "true" : "false"));
|
|
}
|
|
function updateBrightnessLabel() {
|
|
document.getElementById("brightnessValue").innerText = document.getElementById("brightness").value;
|
|
}
|
|
function sendBrightness() {
|
|
var val = document.getElementById("brightness").value;
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "/brightness", true);
|
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
xhr.send("value=" + val);
|
|
}
|
|
function fetchStatus() {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("GET", "/program-status", true);
|
|
xhr.onload = function() {
|
|
if (xhr.status === 200) {
|
|
var s = JSON.parse(xhr.responseText);
|
|
document.getElementById("top_text").checked = s.top_text;
|
|
document.getElementById("bottom_text").checked = s.bottom_text;
|
|
document.getElementById("vehicles").checked = s.vehicles;
|
|
document.getElementById("brightness").value = s.brightness;
|
|
document.getElementById("brightnessValue").innerText = s.brightness;
|
|
}
|
|
};
|
|
xhr.send();
|
|
}
|
|
window.onload = fetchStatus;
|
|
</script>
|
|
</body>
|
|
</html>"""
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return Response(INDEX_HTML, mimetype='text/html')
|
|
|
|
|
|
@app.route('/brightness', methods=['POST'])
|
|
def handle_brightness():
|
|
value = request.form.get('value')
|
|
if value is not None:
|
|
with _lock:
|
|
state['brightness'] = max(0, min(100, int(value)))
|
|
return 'OK'
|
|
|
|
|
|
@app.route('/toggle-program', methods=['POST'])
|
|
def handle_toggle():
|
|
program = request.form.get('program')
|
|
enabled_str = request.form.get('enabled')
|
|
if not program or enabled_str is None:
|
|
return 'Missing program or enabled parameter', 400
|
|
enabled = enabled_str.lower() == 'true'
|
|
key_map = {
|
|
'top_text': 'program_top_text_enabled',
|
|
'bottom_text': 'program_bottom_text_enabled',
|
|
'vehicles': 'program_vehicles_enabled',
|
|
}
|
|
if program not in key_map:
|
|
return 'Unknown program', 400
|
|
with _lock:
|
|
state[key_map[program]] = enabled
|
|
return 'OK'
|
|
|
|
|
|
@app.route('/program-status', methods=['GET'])
|
|
def handle_status():
|
|
with _lock:
|
|
s = dict(state)
|
|
return jsonify({
|
|
'top_text': s['program_top_text_enabled'],
|
|
'bottom_text': s['program_bottom_text_enabled'],
|
|
'vehicles': s['program_vehicles_enabled'],
|
|
'brightness': s['brightness'],
|
|
})
|
|
|
|
|
|
def start_server(host='0.0.0.0', port=80):
|
|
t = threading.Thread(target=lambda: app.run(host=host, port=port, use_reloader=False), daemon=True)
|
|
t.start()
|