web
This commit is contained in:
@@ -18,4 +18,8 @@
|
|||||||
#define SMALL_TEXT_HEIGHT 7
|
#define SMALL_TEXT_HEIGHT 7
|
||||||
#define SMALL_TEXT_WIDTH 5
|
#define SMALL_TEXT_WIDTH 5
|
||||||
#define MEDIUM_TEXT_HEIGHT 7
|
#define MEDIUM_TEXT_HEIGHT 7
|
||||||
#define MEDIUM_TEXT_WIDTH 5
|
#define MEDIUM_TEXT_WIDTH 5
|
||||||
|
|
||||||
|
#define PROGRAM2_TEXT_ITERATIONS 5
|
||||||
|
#define PROGRAM2_TRIGGER_CHANCE 25
|
||||||
|
#define PROGRAM2_VEHICLE_COUNT 25
|
||||||
@@ -4,426 +4,116 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||||||
<title>LED Panel Control</title>
|
<title>LED Panel Control</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
.button {
|
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
|
||||||
background-color: #4CAF50;
|
h1 { text-align: center; margin-bottom: 30px; font-size: 1.5em; }
|
||||||
color: white;
|
.container { max-width: 400px; margin: 0 auto; }
|
||||||
padding: 10px 20px;
|
.card {
|
||||||
text-align: center;
|
background: #16213e;
|
||||||
text-decoration: none;
|
border-radius: 12px;
|
||||||
display: inline-block;
|
padding: 16px 20px;
|
||||||
font-size: 16px;
|
margin-bottom: 12px;
|
||||||
margin: 4px 2px;
|
display: flex;
|
||||||
cursor: pointer;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
textarea {
|
.card-label { font-size: 1em; }
|
||||||
width: 80%;
|
.card-label small { display: block; color: #888; font-size: 0.75em; margin-top: 2px; }
|
||||||
height: 200px;
|
.toggle { position: relative; width: 52px; height: 28px; flex-shrink: 0; }
|
||||||
margin-top: 20px;
|
.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;
|
||||||
}
|
}
|
||||||
.text-controls {
|
.toggle .slider:before {
|
||||||
margin-top: 20px;
|
content: ""; position: absolute;
|
||||||
border: 1px solid #ccc;
|
height: 22px; width: 22px;
|
||||||
padding: 10px;
|
left: 3px; bottom: 3px;
|
||||||
margin-bottom: 10px;
|
background: #fff; border-radius: 50%;
|
||||||
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
.text-input-group {
|
.toggle input:checked + .slider { background: #4CAF50; }
|
||||||
margin-bottom: 10px;
|
.toggle input:checked + .slider:before { transform: translateX(24px); }
|
||||||
}
|
.brightness-card {
|
||||||
.param-explanation {
|
background: #16213e;
|
||||||
font-size: 0.8em;
|
border-radius: 12px;
|
||||||
color: #666;
|
padding: 16px 20px;
|
||||||
}
|
margin-bottom: 12px;
|
||||||
pre {
|
|
||||||
text-align: left;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
padding: 10px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
#nodes-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.node-item {
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.modal {
|
|
||||||
display: none; /* Hidden by default */
|
|
||||||
position: fixed; /* Stay in place */
|
|
||||||
z-index: 1; /* Sit on top */
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%; /* Full width */
|
|
||||||
height: 100%; /* Full height */
|
|
||||||
overflow: auto; /* Enable scroll if needed */
|
|
||||||
background-color: rgb(0,0,0); /* Fallback color */
|
|
||||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
background-color: #fefefe;
|
|
||||||
margin: 15% auto; /* 15% from the top and centered */
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #888;
|
|
||||||
width: 80%; /* Could be more or less, depending on screen size */
|
|
||||||
}
|
|
||||||
.close {
|
|
||||||
color: #aaa;
|
|
||||||
float: right;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.close:hover,
|
|
||||||
.close:focus {
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>LED Panel Control</h1>
|
<div class="container">
|
||||||
<button class="button" onclick="location.href='/upload-page'">Upload Image</button>
|
<h1>LED Panel Control</h1>
|
||||||
<br>
|
|
||||||
<div>
|
|
||||||
<label for="brightness">Brightness: </label>
|
|
||||||
<input type="range" id="brightness" min="0" max="100" value="100" onchange="updateBrightness()">
|
|
||||||
<span id="brightnessValue">100</span>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div id="nodes-container">
|
<div class="card">
|
||||||
<h2>Current Text Nodes</h2>
|
<div class="card-label">Top Text<small>Witamy w PTI</small></div>
|
||||||
<div id="nodes-list"></div>
|
<label class="toggle"><input type="checkbox" id="top_text" checked onchange="toggle('top_text', this.checked)"><span class="slider"></span></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="modify-modal" class="modal">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-label">Bottom Text<small>School programs</small></div>
|
||||||
<span class="close" onclick="closeModal()">×</span>
|
<label class="toggle"><input type="checkbox" id="bottom_text" checked onchange="toggle('bottom_text', this.checked)"><span class="slider"></span></label>
|
||||||
<h3>Modify Text Node</h3>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="hidden" id="modifyGlobalIdInput">
|
|
||||||
<b>Node Global ID:</b> <span id="modifyGlobalIdDisplay"></span>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="text" id="modifyTextInput" placeholder="New text...">
|
|
||||||
<div class="param-explanation">New text for the node.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="color" id="modifyColorPicker">
|
|
||||||
<div class="param-explanation">New color for the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="modifySlownessInput" placeholder="Slowness" min="0" max="255">
|
|
||||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-buttons">
|
|
||||||
<button class="button" onclick="modifyText()">Save Changes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="modify-multicolor-modal" class="modal">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-label">Vehicles<small>Vehicle interruptions</small></div>
|
||||||
<span class="close" onclick="closeMultiColorModal()">×</span>
|
<label class="toggle"><input type="checkbox" id="vehicles" checked onchange="toggle('vehicles', this.checked)"><span class="slider"></span></label>
|
||||||
<h3>Modify Multi-Color Text Node</h3>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="hidden" id="modifyMultiColorGlobalIdInput">
|
|
||||||
<b>Node Global ID:</b> <span id="modifyMultiColorGlobalIdDisplay"></span>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="text" id="modifyMultiColorTextInput" placeholder="New text...">
|
|
||||||
<div class="param-explanation">New text for the node.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="text" id="modifyMultiColorColorInput" placeholder="e.g., #ff0000,#00ff00,#0000ff">
|
|
||||||
<div class="param-explanation">Comma-separated list of hex colors (max 4).</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-buttons">
|
|
||||||
<button class="button" onclick="modifyMultiColorText()">Save Changes</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-controls">
|
<div class="brightness-card">
|
||||||
<h3>Add Text Node (Full Control)</h3>
|
<label>Brightness</label>
|
||||||
<pre><code>void addNewTextNode(char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
|
<div class="brightness-row">
|
||||||
<div class="text-input-group">
|
<input type="range" id="brightness" min="0" max="100" value="100" oninput="updateBrightnessLabel()" onchange="sendBrightness()">
|
||||||
<input type="text" id="textInput" placeholder="Enter text...">
|
<span id="brightnessValue">100</span>
|
||||||
<div class="param-explanation">The text to display.</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="color" id="textColorPicker" value="#ffffff">
|
|
||||||
<div class="param-explanation">Color of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="xInput" placeholder="X position">
|
|
||||||
<div class="param-explanation">X coordinate of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="yInput" placeholder="Y position">
|
|
||||||
<div class="param-explanation">Y coordinate of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<select id="fontSizeInput">
|
|
||||||
<option value="small">Small</option>
|
|
||||||
<option value="medium">Medium</option>
|
|
||||||
</select>
|
|
||||||
<div class="param-explanation">Font size of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="slownessInput" placeholder="Slowness" value="2" min="0" max="255">
|
|
||||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="checkbox" id="isRepeatingInput">
|
|
||||||
<label for="isRepeatingInput">Repeating</label>
|
|
||||||
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-buttons">
|
|
||||||
<button class="button" onclick="sendText()">Add Text</button>
|
|
||||||
<button class="button" onclick="sendText('top')">Send to Top</button>
|
|
||||||
<button class="button" onclick="sendText('bottom')">Send to Bottom</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-controls">
|
|
||||||
<h3>Add Multicolor Text Node</h3>
|
|
||||||
<pre><code>void addNewMultiColor(char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="text" id="multicolorTextInput" placeholder="Enter text...">
|
|
||||||
<div class="param-explanation">The text to display.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="text" id="multicolorColorInput" placeholder="e.g., #ff0000,#00ff00,#0000ff">
|
|
||||||
<div class="param-explanation">Comma-separated list of hex colors (max 4).</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="multicolorXInput" placeholder="X position">
|
|
||||||
<div class="param-explanation">X coordinate of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="multicolorYInput" placeholder="Y position">
|
|
||||||
<div class="param-explanation">Y coordinate of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<select id="multicolorFontSizeInput">
|
|
||||||
<option value="small">Small</option>
|
|
||||||
<option value="medium">Medium</option>
|
|
||||||
</select>
|
|
||||||
<div class="param-explanation">Font size of the text.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="number" id="multicolorSlownessInput" placeholder="Slowness" value="2" min="0" max="255">
|
|
||||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-input-group">
|
|
||||||
<input type="checkbox" id="multicolorIsRepeatingInput">
|
|
||||||
<label for="multicolorIsRepeatingInput">Repeating</label>
|
|
||||||
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-buttons">
|
|
||||||
<button class="button" onclick="sendMulticolorText()">Add Multicolor Text</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function updateBrightness() {
|
function toggle(program, enabled) {
|
||||||
var brightness = document.getElementById("brightness").value;
|
var xhr = new XMLHttpRequest();
|
||||||
document.getElementById("brightnessValue").innerText = brightness;
|
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();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", "/brightness", true);
|
xhr.open("POST", "/brightness", true);
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||||
xhr.send("value=" + brightness);
|
xhr.send("value=" + val);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendText(position) {
|
function fetchStatus() {
|
||||||
const text = document.getElementById('textInput').value;
|
|
||||||
const color = document.getElementById('textColorPicker').value;
|
|
||||||
const slowness = document.getElementById('slownessInput').value;
|
|
||||||
const x = document.getElementById('xInput').value;
|
|
||||||
const y = document.getElementById('yInput').value;
|
|
||||||
const fontSize = document.getElementById('fontSizeInput').value;
|
|
||||||
const isRepeating = document.getElementById('isRepeatingInput').checked;
|
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
alert('Please enter some text.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", "/text", true);
|
xhr.open("GET", "/program-status", true);
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
let data = "text=" + encodeURIComponent(text) + "&color=" + encodeURIComponent(color) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
|
|
||||||
if(position) {
|
|
||||||
data += "&position=" + position;
|
|
||||||
} else {
|
|
||||||
if(x) data += "&x=" + x;
|
|
||||||
if(y) data += "&y=" + y;
|
|
||||||
}
|
|
||||||
if(fontSize) data += "&fontSize=" + fontSize;
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
fetchNodes();
|
var s = JSON.parse(xhr.responseText);
|
||||||
}
|
document.getElementById("top_text").checked = s.top_text;
|
||||||
};
|
document.getElementById("bottom_text").checked = s.bottom_text;
|
||||||
xhr.send(data);
|
document.getElementById("vehicles").checked = s.vehicles;
|
||||||
}
|
document.getElementById("brightness").value = s.brightness;
|
||||||
|
document.getElementById("brightnessValue").innerText = s.brightness;
|
||||||
function sendMulticolorText() {
|
|
||||||
const text = document.getElementById('multicolorTextInput').value;
|
|
||||||
const colors = document.getElementById('multicolorColorInput').value;
|
|
||||||
const slowness = document.getElementById('multicolorSlownessInput').value;
|
|
||||||
const x = document.getElementById('multicolorXInput').value;
|
|
||||||
const y = document.getElementById('multicolorYInput').value;
|
|
||||||
const fontSize = document.getElementById('multicolorFontSizeInput').value;
|
|
||||||
const isRepeating = document.getElementById('multicolorIsRepeatingInput').checked;
|
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
alert('Please enter some text.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!colors) {
|
|
||||||
alert('Please enter some colors.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("POST", "/multicolor-text", true);
|
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
let data = "text=" + encodeURIComponent(text) + "&colors=" + encodeURIComponent(colors) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
|
|
||||||
if(x) data += "&x=" + x;
|
|
||||||
if(y) data += "&y=" + y;
|
|
||||||
if(fontSize) data += "&fontSize=" + fontSize;
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
fetchNodes();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifyText() {
|
|
||||||
const global_id = document.getElementById('modifyGlobalIdInput').value;
|
|
||||||
const color = document.getElementById('modifyColorPicker').value;
|
|
||||||
const text = document.getElementById('modifyTextInput').value;
|
|
||||||
const slowness = document.getElementById('modifySlownessInput').value;
|
|
||||||
|
|
||||||
if (!global_id) {
|
|
||||||
alert('Please enter a node ID.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("POST", "/modify-text", true);
|
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
let data = "global_id=" + global_id + "&color=" + encodeURIComponent(color) + "&text=" + encodeURIComponent(text) + "&slowness=" + slowness;
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
fetchNodes();
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchNodes() {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("GET", "/nodes", true);
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
var nodes = JSON.parse(this.responseText);
|
|
||||||
var nodesList = document.getElementById('nodes-list');
|
|
||||||
nodesList.innerHTML = '';
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
var node = nodes[i];
|
|
||||||
var nodeDiv = document.createElement('div');
|
|
||||||
nodeDiv.className = 'node-item';
|
|
||||||
if (node.type === 'text') {
|
|
||||||
var color = '#' + ('000000' + node.color.toString(16)).slice(-6);
|
|
||||||
nodeDiv.innerHTML = '<b>ID:</b> ' + node.global_id + ', <b>Text:</b> ' + node.text + ', <b>Color:</b> <span style="color:' + color + '">' + color + '</span>' +
|
|
||||||
'<button style="margin-left: 10px;" onclick="populateModifyForm(' + node.global_id + ', \'' + color + '\', \'' + node.text + '\', ' + node.slowness + ')">Modify</button>';
|
|
||||||
} else if (node.type === 'multi-color') {
|
|
||||||
var colorsStr = node.colors.map(c => '#' + ('000000' + ((c.r << 16) | (c.g << 8) | c.b).toString(16)).slice(-6)).join(',');
|
|
||||||
nodeDiv.innerHTML = '<b>ID:</b> ' + node.global_id + ', <b>Text:</b> ' + node.text + ', <b>Type:</b> Multi-Color' +
|
|
||||||
'<button style="margin-left: 10px;" onclick="populateMultiColorModifyForm(' + node.global_id + ', \'' + colorsStr + '\', \'' + node.text + '\')">Modify</button>';
|
|
||||||
}
|
|
||||||
nodesList.appendChild(nodeDiv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateModifyForm(global_id, color, text, slowness) {
|
window.onload = fetchStatus;
|
||||||
document.getElementById('modifyGlobalIdInput').value = global_id;
|
|
||||||
document.getElementById('modifyGlobalIdDisplay').innerText = global_id;
|
|
||||||
document.getElementById('modifyColorPicker').value = color;
|
|
||||||
document.getElementById('modifyTextInput').value = text;
|
|
||||||
document.getElementById('modifySlownessInput').value = slowness;
|
|
||||||
document.getElementById('modify-modal').style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
document.getElementById('modify-modal').style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateMultiColorModifyForm(global_id, colors, text) {
|
|
||||||
document.getElementById('modifyMultiColorGlobalIdInput').value = global_id;
|
|
||||||
document.getElementById('modifyMultiColorGlobalIdDisplay').innerText = global_id;
|
|
||||||
document.getElementById('modifyMultiColorColorInput').value = colors;
|
|
||||||
document.getElementById('modifyMultiColorTextInput').value = text;
|
|
||||||
document.getElementById('modify-multicolor-modal').style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeMultiColorModal() {
|
|
||||||
document.getElementById('modify-multicolor-modal').style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifyMultiColorText() {
|
|
||||||
const global_id = document.getElementById('modifyMultiColorGlobalIdInput').value;
|
|
||||||
const colors = document.getElementById('modifyMultiColorColorInput').value;
|
|
||||||
const text = document.getElementById('modifyMultiColorTextInput').value;
|
|
||||||
|
|
||||||
if (!global_id) {
|
|
||||||
alert('Please enter a node ID.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("POST", "/modify-multicolor-text", true);
|
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
let data = "global_id=" + global_id + "&colors=" + encodeURIComponent(colors) + "&text=" + encodeURIComponent(text);
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
fetchNodes();
|
|
||||||
closeMultiColorModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onclick = function(event) {
|
|
||||||
if (event.target == document.getElementById('modify-modal')) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
if (event.target == document.getElementById('modify-multicolor-modal')) {
|
|
||||||
closeMultiColorModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(fetchNodes, 5000);
|
|
||||||
window.onload = fetchNodes;
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)rawliteral";
|
)rawliteral";
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "index.h"
|
#include "index.h"
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
#include "lowLevel.h"
|
#include "lowLevel.h"
|
||||||
#include "upload_page.h"
|
|
||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
|
|
||||||
#ifdef __AVR__
|
#ifdef __AVR__
|
||||||
@@ -25,6 +24,16 @@ unsigned char bottom_text_state = 0;
|
|||||||
unsigned char scroll_node_global_id = 0;
|
unsigned char scroll_node_global_id = 0;
|
||||||
bool scroll_node_active = false;
|
bool scroll_node_active = false;
|
||||||
|
|
||||||
|
bool program2_active = false;
|
||||||
|
unsigned char bottom_text_iteration_count = 0;
|
||||||
|
|
||||||
|
bool program_top_text_enabled = true;
|
||||||
|
bool program_bottom_text_enabled = true;
|
||||||
|
bool program_vehicles_enabled = true;
|
||||||
|
unsigned char program2_vehicles_spawned = 0;
|
||||||
|
short program2_drain_counter = 0;
|
||||||
|
bool program2_done = false;
|
||||||
|
|
||||||
Image saved_images[MAX_IMAGES_SAVED] = {
|
Image saved_images[MAX_IMAGES_SAVED] = {
|
||||||
{},{}
|
{},{}
|
||||||
};
|
};
|
||||||
@@ -526,7 +535,20 @@ void resetGlobalIdToAfterIntroValue()
|
|||||||
|
|
||||||
void handleProgram1()
|
void handleProgram1()
|
||||||
{
|
{
|
||||||
if (!main_animation_started)
|
// --- Top text section ---
|
||||||
|
if (!program_top_text_enabled)
|
||||||
|
{
|
||||||
|
// Kill existing multi-color node if any
|
||||||
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
||||||
|
{
|
||||||
|
if (multi_color_text_node[i].disappear_time != 0)
|
||||||
|
{
|
||||||
|
multi_color_text_node[i].disappear_time = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main_animation_started = false;
|
||||||
|
}
|
||||||
|
else if (!main_animation_started)
|
||||||
{
|
{
|
||||||
if (!isNodeExistingByGlobal(0) && !isNodeExistingByGlobal(1))
|
if (!isNodeExistingByGlobal(0) && !isNodeExistingByGlobal(1))
|
||||||
{
|
{
|
||||||
@@ -541,10 +563,54 @@ void handleProgram1()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Bottom text section ---
|
||||||
|
if (!program_bottom_text_enabled)
|
||||||
|
{
|
||||||
|
// Kill active scroll node if any
|
||||||
|
if (scroll_node_active)
|
||||||
|
{
|
||||||
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
||||||
|
{
|
||||||
|
if (text_nodes[i].disappear_time != 0)
|
||||||
|
{
|
||||||
|
text_nodes[i].disappear_time = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scroll_node_active = false;
|
||||||
|
}
|
||||||
|
if (program2_active)
|
||||||
|
{
|
||||||
|
program2_active = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip bottom text management if vehicles are active
|
||||||
|
if (program2_active) return;
|
||||||
|
|
||||||
// Bottom row: alternating scrolling texts
|
// Bottom row: alternating scrolling texts
|
||||||
if (main_animation_started &&
|
if ((main_animation_started || !program_top_text_enabled) &&
|
||||||
(!scroll_node_active || !isNodeExistingByGlobal(scroll_node_global_id)))
|
(!scroll_node_active || !isNodeExistingByGlobal(scroll_node_global_id)))
|
||||||
{
|
{
|
||||||
|
// A bottom text just finished — check if we should trigger vehicles
|
||||||
|
if (scroll_node_active)
|
||||||
|
{
|
||||||
|
bottom_text_iteration_count++;
|
||||||
|
scroll_node_active = false;
|
||||||
|
|
||||||
|
if (program_vehicles_enabled && bottom_text_iteration_count >= PROGRAM2_TEXT_ITERATIONS)
|
||||||
|
{
|
||||||
|
bottom_text_iteration_count = 0;
|
||||||
|
if ((unsigned char)random(0, 100) < PROGRAM2_TRIGGER_CHANCE)
|
||||||
|
{
|
||||||
|
program2_active = true;
|
||||||
|
resetProgram2();
|
||||||
|
fillPixels(0, 9, DISPLAY_MAX_X, DISPLAY_MAX_Y, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextNode* node = nullptr;
|
TextNode* node = nullptr;
|
||||||
if (bottom_text_state == 0)
|
if (bottom_text_state == 0)
|
||||||
{
|
{
|
||||||
@@ -596,7 +662,16 @@ void drawVehichle(unsigned char index = 0, short pos_x = 0, short pos_y = 0)
|
|||||||
unsigned char program2_scroll_progress = 0;
|
unsigned char program2_scroll_progress = 0;
|
||||||
short program2_spacing_counter = 0;
|
short program2_spacing_counter = 0;
|
||||||
|
|
||||||
void handleProgram2(unsigned char spacing = 4, unsigned char scroll_slowness = 1, short y = 0)
|
void resetProgram2()
|
||||||
|
{
|
||||||
|
program2_scroll_progress = 0;
|
||||||
|
program2_spacing_counter = 0;
|
||||||
|
program2_vehicles_spawned = 0;
|
||||||
|
program2_drain_counter = 0;
|
||||||
|
program2_done = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleProgram2(unsigned char spacing = 4, unsigned char scroll_slowness = 1, short y = 0, unsigned char max_vehicles = 0)
|
||||||
{
|
{
|
||||||
if (scroll_slowness > program2_scroll_progress)
|
if (scroll_slowness > program2_scroll_progress)
|
||||||
{
|
{
|
||||||
@@ -607,11 +682,26 @@ void handleProgram2(unsigned char spacing = 4, unsigned char scroll_slowness = 1
|
|||||||
|
|
||||||
shiftGivenRectangleLeft(0, y, DISPLAY_MAX_X, y + 3, 1);
|
shiftGivenRectangleLeft(0, y, DISPLAY_MAX_X, y + 3, 1);
|
||||||
|
|
||||||
|
// If we have a vehicle limit and reached it, drain remaining vehicles off screen
|
||||||
|
if (max_vehicles > 0 && program2_vehicles_spawned >= max_vehicles)
|
||||||
|
{
|
||||||
|
program2_drain_counter++;
|
||||||
|
if (program2_drain_counter >= DISPLAY_MAX_X + 1)
|
||||||
|
{
|
||||||
|
program2_done = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
program2_spacing_counter--;
|
program2_spacing_counter--;
|
||||||
if (program2_spacing_counter <= 0)
|
if (program2_spacing_counter <= 0)
|
||||||
{
|
{
|
||||||
drawVehichle(random(0, 11), DISPLAY_MAX_X - 7, y);
|
drawVehichle(random(0, 11), DISPLAY_MAX_X - 7, y);
|
||||||
program2_spacing_counter = 8 + spacing;
|
program2_spacing_counter = 8 + spacing;
|
||||||
|
if (max_vehicles > 0)
|
||||||
|
{
|
||||||
|
program2_vehicles_spawned++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,13 +722,50 @@ void loop()
|
|||||||
{
|
{
|
||||||
handle_server();
|
handle_server();
|
||||||
|
|
||||||
handleProgram2(4, 1, 0);
|
// Clear top area for text redraw
|
||||||
|
fillPixels(0, 0, DISPLAY_MAX_X, 7, 0);
|
||||||
|
|
||||||
|
// Manage top text + bottom state machine
|
||||||
|
handleProgram1();
|
||||||
|
|
||||||
|
// Draw/scroll top "Witamy w PTI" (skip if disabled — area already cleared)
|
||||||
|
if (program_top_text_enabled)
|
||||||
|
{
|
||||||
|
scrollAllMultiColorTexts(true);
|
||||||
|
handleMultiColorDisappearTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle bottom text disable mid-frame
|
||||||
|
if (!program_bottom_text_enabled)
|
||||||
|
{
|
||||||
|
if (program2_active)
|
||||||
|
{
|
||||||
|
program2_active = false;
|
||||||
|
}
|
||||||
|
fillPixels(0, 9, DISPLAY_MAX_X, DISPLAY_MAX_Y, 0);
|
||||||
|
}
|
||||||
|
// Handle vehicles disabled while vehicle animation active
|
||||||
|
else if (program2_active && !program_vehicles_enabled)
|
||||||
|
{
|
||||||
|
program2_active = false;
|
||||||
|
fillPixels(0, 9, DISPLAY_MAX_X, DISPLAY_MAX_Y, 0);
|
||||||
|
}
|
||||||
|
else if (program2_active)
|
||||||
|
{
|
||||||
|
handleProgram2(4, 1, 12, PROGRAM2_VEHICLE_COUNT);
|
||||||
|
if (program2_done)
|
||||||
|
{
|
||||||
|
program2_active = false;
|
||||||
|
fillPixels(0, 9, DISPLAY_MAX_X, DISPLAY_MAX_Y, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clear bottom area for text redraw
|
||||||
|
fillPixels(0, 9, DISPLAY_MAX_X, DISPLAY_MAX_Y, 0);
|
||||||
|
scrollAllScrollableTexts(true);
|
||||||
|
handleDisappearTimers();
|
||||||
|
}
|
||||||
|
|
||||||
// pixels.clear();
|
|
||||||
// handleProgram1();
|
|
||||||
// handleDisappearTimers();
|
|
||||||
// handleMultiColorDisappearTimers();
|
|
||||||
// scrollAllScrollableTexts(true);
|
|
||||||
// scrollAllMultiColorTexts(true);
|
|
||||||
pixels.show();
|
pixels.show();
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,10 @@ extern Cursor cursor;
|
|||||||
extern unsigned char brightness;
|
extern unsigned char brightness;
|
||||||
extern Animation animations[MAX_ANIMATIONS_COUNT];
|
extern Animation animations[MAX_ANIMATIONS_COUNT];
|
||||||
|
|
||||||
|
extern bool program_top_text_enabled;
|
||||||
|
extern bool program_bottom_text_enabled;
|
||||||
|
extern bool program_vehicles_enabled;
|
||||||
|
|
||||||
void displayAnimationFrame(unsigned char animation_index);
|
void displayAnimationFrame(unsigned char animation_index);
|
||||||
void tickAnimations();
|
void tickAnimations();
|
||||||
void playAnimation(unsigned char index, unsigned char frame_delay, bool loop);
|
void playAnimation(unsigned char index, unsigned char frame_delay, bool loop);
|
||||||
@@ -35,6 +39,8 @@ void modifyTextNodeByGlobalId(unsigned char global_id, uint32_t new_color, char
|
|||||||
void modifyMultiColorTextNodeByGlobalId(unsigned char global_id, char new_text[TEXT_MAX_LENGTH + 1], RGBWithIndex new_colors[4], unsigned char new_color_count);
|
void modifyMultiColorTextNodeByGlobalId(unsigned char global_id, char new_text[TEXT_MAX_LENGTH + 1], RGBWithIndex new_colors[4], unsigned char new_color_count);
|
||||||
bool isNodeExistingByGlobal(unsigned char global_id);
|
bool isNodeExistingByGlobal(unsigned char global_id);
|
||||||
void handleProgram1();
|
void handleProgram1();
|
||||||
|
void handleProgram2(unsigned char spacing, unsigned char scroll_slowness, short y, unsigned char max_vehicles);
|
||||||
|
void resetProgram2();
|
||||||
|
|
||||||
|
|
||||||
#endif // PROTOTYPES_H
|
#endif // PROTOTYPES_H
|
||||||
+41
-333
@@ -2,7 +2,6 @@
|
|||||||
#include <WebServer.h>
|
#include <WebServer.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "structs.h"
|
#include "structs.h"
|
||||||
#include "upload_page.h"
|
|
||||||
#include "prototypes.h"
|
#include "prototypes.h"
|
||||||
|
|
||||||
const char* ssid = "PPIA";
|
const char* ssid = "PPIA";
|
||||||
@@ -11,12 +10,6 @@ unsigned char brightness = 100;
|
|||||||
|
|
||||||
WebServer server(80);
|
WebServer server(80);
|
||||||
|
|
||||||
Pixel new_image[16][16];
|
|
||||||
char* fileContent = nullptr;
|
|
||||||
size_t fileContent_len = 0;
|
|
||||||
String upload_error_message = "";
|
|
||||||
|
|
||||||
|
|
||||||
void handleBrightness() {
|
void handleBrightness() {
|
||||||
if (server.hasArg("value")) {
|
if (server.hasArg("value")) {
|
||||||
brightness = server.arg("value").toInt();
|
brightness = server.arg("value").toInt();
|
||||||
@@ -26,327 +19,53 @@ void handleBrightness() {
|
|||||||
|
|
||||||
void handleRoot()
|
void handleRoot()
|
||||||
{
|
{
|
||||||
server.send(200, "text/html", index_html);
|
size_t len = strlen(index_html);
|
||||||
}
|
server.setContentLength(len);
|
||||||
|
server.send(200, "text/html", "");
|
||||||
void handleUploadPage()
|
const size_t CHUNK = 256;
|
||||||
{
|
for (size_t i = 0; i < len; i += CHUNK) {
|
||||||
server.send(200, "text/html", upload_page_html);
|
size_t n = (len - i < CHUNK) ? len - i : CHUNK;
|
||||||
}
|
server.client().write((const uint8_t*)(index_html + i), n);
|
||||||
|
|
||||||
|
|
||||||
void handleBmpUpload() {
|
|
||||||
HTTPUpload& upload = server.upload();
|
|
||||||
if (upload.status == UPLOAD_FILE_START) {
|
|
||||||
upload_error_message = "";
|
|
||||||
if (fileContent) {
|
|
||||||
free(fileContent);
|
|
||||||
fileContent = nullptr;
|
|
||||||
fileContent_len = 0;
|
|
||||||
}
|
|
||||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
|
||||||
if (upload.name == "image") {
|
|
||||||
fileContent = (char*)realloc(fileContent, fileContent_len + upload.currentSize);
|
|
||||||
memcpy(fileContent + fileContent_len, upload.buf, upload.currentSize);
|
|
||||||
fileContent_len += upload.currentSize;
|
|
||||||
}
|
|
||||||
} else if (upload.status == UPLOAD_FILE_END) {
|
|
||||||
if (upload.name == "image") {
|
|
||||||
// Read BMP header
|
|
||||||
char header[54];
|
|
||||||
memcpy(header, fileContent, 54);
|
|
||||||
|
|
||||||
int dataOffset = *(int*)&header[10];
|
|
||||||
int width = *(int*)&header[18];
|
|
||||||
int height = *(int*)&header[22];
|
|
||||||
short bitsPerPixel = *(short*)&header[28];
|
|
||||||
|
|
||||||
if (bitsPerPixel != 24) {
|
|
||||||
upload_error_message = "Unsupported BMP format: Only 24-bit BMPs are supported";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < 1 || width > 48 || height < 1 || height > 16) {
|
|
||||||
upload_error_message = "Invalid image dimensions";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saved_images[0].width = width;
|
|
||||||
saved_images[0].height = height;
|
|
||||||
|
|
||||||
// Read pixel data
|
|
||||||
int row_padded = (width*3 + 3) & (~3);
|
|
||||||
char* pixel_data = fileContent + dataOffset;
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int bmp_row = height - 1 - y;
|
|
||||||
saved_images[0].pixels[y][x].b = pixel_data[bmp_row*row_padded + x*3 + 0];
|
|
||||||
saved_images[0].pixels[y][x].g = pixel_data[bmp_row*row_padded + x*3 + 1];
|
|
||||||
saved_images[0].pixels[y][x].r = pixel_data[bmp_row*row_padded + x*3 + 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(fileContent);
|
|
||||||
fileContent = nullptr;
|
|
||||||
fileContent_len = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleToggleProgram() {
|
||||||
|
if (server.hasArg("program") && server.hasArg("enabled")) {
|
||||||
|
String program = server.arg("program");
|
||||||
|
bool enabled = server.arg("enabled") == "true";
|
||||||
|
|
||||||
|
if (program == "top_text") {
|
||||||
|
program_top_text_enabled = enabled;
|
||||||
void handleText() {
|
} else if (program == "bottom_text") {
|
||||||
if (server.hasArg("text") && server.hasArg("color") && server.hasArg("slowness")) {
|
program_bottom_text_enabled = enabled;
|
||||||
String text_str = server.arg("text");
|
} else if (program == "vehicles") {
|
||||||
String colorStr = server.arg("color");
|
program_vehicles_enabled = enabled;
|
||||||
unsigned char slowness = server.arg("slowness").toInt();
|
|
||||||
short disappear_time = server.hasArg("disappear") ? server.arg("disappear").toInt() : -1;
|
|
||||||
bool is_small = true;
|
|
||||||
if (server.hasArg("fontSize") && server.arg("fontSize") != "") {
|
|
||||||
if (server.arg("fontSize") == "medium") {
|
|
||||||
is_small = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_repeating = false;
|
|
||||||
if (server.hasArg("is_repeating")) {
|
|
||||||
is_repeating = server.arg("is_repeating") == "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
char text[TEXT_MAX_LENGTH + 1];
|
|
||||||
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
|
|
||||||
|
|
||||||
uint32_t color = strtol(colorStr.substring(1).c_str(), NULL, 16);
|
|
||||||
|
|
||||||
if (server.hasArg("position")) {
|
|
||||||
String position = server.arg("position");
|
|
||||||
if (position == "top") {
|
|
||||||
addNewTextNode(text, color, false, 43, 0, slowness, true, is_small, disappear_time, is_repeating);
|
|
||||||
} else if (position == "bottom") {
|
|
||||||
addNewTextNode(text, color, false, -text_str.length() * 6, 9, slowness, true, is_small, disappear_time, is_repeating);
|
|
||||||
}
|
|
||||||
server.send(200, "text/plain", "OK");
|
|
||||||
} else {
|
} else {
|
||||||
short x = 0;
|
server.send(400, "text/plain", "Unknown program");
|
||||||
short y = 0;
|
return;
|
||||||
if (server.hasArg("x") && server.arg("x") != "") {
|
|
||||||
x = server.arg("x").toInt();
|
|
||||||
}
|
|
||||||
if (server.hasArg("y") && server.arg("y") != "") {
|
|
||||||
y = server.arg("y").toInt();
|
|
||||||
}
|
|
||||||
addNewTextNode(text, color, false, x, y, slowness, true, is_small, disappear_time, is_repeating);
|
|
||||||
server.send(200, "text/plain", "OK");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
server.send(400, "text/plain", "Invalid arguments for /text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleMulticolorText() {
|
|
||||||
if (server.hasArg("text") && server.hasArg("colors") && server.hasArg("slowness")) {
|
|
||||||
String text_str = server.arg("text");
|
|
||||||
String colors_str = server.arg("colors");
|
|
||||||
unsigned char slowness = server.arg("slowness").toInt();
|
|
||||||
short disappear_time = server.hasArg("disappear") ? server.arg("disappear").toInt() : -1;
|
|
||||||
bool is_small = true;
|
|
||||||
if (server.hasArg("fontSize") && server.arg("fontSize") != "") {
|
|
||||||
if (server.arg("fontSize") == "medium") {
|
|
||||||
is_small = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool is_repeating = false;
|
|
||||||
if (server.hasArg("is_repeating")) {
|
|
||||||
is_repeating = server.arg("is_repeating") == "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
char text[TEXT_MAX_LENGTH + 1];
|
|
||||||
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
|
|
||||||
|
|
||||||
RGBWithIndex colors[4];
|
|
||||||
int color_count = 0;
|
|
||||||
String color_part = "";
|
|
||||||
int last_comma = -1;
|
|
||||||
for (int i = 0; i < colors_str.length() && color_count < 4; i++) {
|
|
||||||
if (colors_str.charAt(i) == ',') {
|
|
||||||
color_part = colors_str.substring(last_comma + 1, i);
|
|
||||||
last_comma = i;
|
|
||||||
if (color_part.length() > 0) {
|
|
||||||
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
|
|
||||||
colors[color_count].r = (color_val >> 16) & 0xFF;
|
|
||||||
colors[color_count].g = (color_val >> 8) & 0xFF;
|
|
||||||
colors[color_count].b = color_val & 0xFF;
|
|
||||||
color_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// last color
|
|
||||||
if (color_count < 4) {
|
|
||||||
color_part = colors_str.substring(last_comma + 1);
|
|
||||||
if (color_part.length() > 0) {
|
|
||||||
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
|
|
||||||
colors[color_count].r = (color_val >> 16) & 0xFF;
|
|
||||||
colors[color_count].g = (color_val >> 8) & 0xFF;
|
|
||||||
colors[color_count].b = color_val & 0xFF;
|
|
||||||
color_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color_count > 0) {
|
|
||||||
int text_len = text_str.length();
|
|
||||||
int part_len = text_len / color_count;
|
|
||||||
for (int i = 0; i < color_count; i++) {
|
|
||||||
colors[i].start_index = i * part_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
short x = 0;
|
|
||||||
short y = 0;
|
|
||||||
if (server.hasArg("x") && server.arg("x") != "") {
|
|
||||||
x = server.arg("x").toInt();
|
|
||||||
}
|
|
||||||
if (server.hasArg("y") && server.arg("y") != "") {
|
|
||||||
y = server.arg("y").toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewMultiColor(text, colors, color_count, false, x, y, slowness, true, is_small, disappear_time, is_repeating);
|
|
||||||
|
|
||||||
server.send(200, "text/plain", "OK");
|
server.send(200, "text/plain", "OK");
|
||||||
} else {
|
} else {
|
||||||
server.send(400, "text/plain", "Invalid arguments for /multicolor-text");
|
server.send(400, "text/plain", "Missing program or enabled parameter");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void handleModifyText()
|
|
||||||
{
|
void handleProgramStatus() {
|
||||||
if (server.hasArg("global_id") && server.hasArg("color"))
|
StaticJsonDocument<128> doc;
|
||||||
{
|
doc["top_text"] = program_top_text_enabled;
|
||||||
unsigned char global_id = server.arg("global_id").toInt();
|
doc["bottom_text"] = program_bottom_text_enabled;
|
||||||
String colorStr = server.arg("color");
|
doc["vehicles"] = program_vehicles_enabled;
|
||||||
uint32_t color = strtol(colorStr.substring(1).c_str(), NULL, 16);
|
doc["brightness"] = brightness;
|
||||||
String text_str = server.arg("text");
|
|
||||||
char text[TEXT_MAX_LENGTH + 1];
|
String json;
|
||||||
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
|
serializeJson(doc, json);
|
||||||
unsigned char slowness = server.arg("slowness").toInt();
|
server.send(200, "application/json", json);
|
||||||
modifyTextNodeByGlobalId(global_id, color, text, slowness);
|
}
|
||||||
server.send(200, "text/plain", "OK");
|
|
||||||
}
|
void start_server()
|
||||||
else
|
{
|
||||||
{
|
WiFi.begin(ssid, password);
|
||||||
server.send(400, "text/plain", "Invalid arguments for /modify-text");
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleModifyMultiColorText()
|
|
||||||
{
|
|
||||||
if (server.hasArg("global_id") && server.hasArg("colors") && server.hasArg("text"))
|
|
||||||
{
|
|
||||||
unsigned char global_id = server.arg("global_id").toInt();
|
|
||||||
String colors_str = server.arg("colors");
|
|
||||||
String text_str = server.arg("text");
|
|
||||||
char text[TEXT_MAX_LENGTH + 1];
|
|
||||||
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
|
|
||||||
|
|
||||||
RGBWithIndex colors[4];
|
|
||||||
int color_count = 0;
|
|
||||||
String color_part = "";
|
|
||||||
int last_comma = -1;
|
|
||||||
for (int i = 0; i < colors_str.length() && color_count < 4; i++) {
|
|
||||||
if (colors_str.charAt(i) == ',') {
|
|
||||||
color_part = colors_str.substring(last_comma + 1, i);
|
|
||||||
last_comma = i;
|
|
||||||
if (color_part.length() > 0) {
|
|
||||||
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
|
|
||||||
colors[color_count].r = (color_val >> 16) & 0xFF;
|
|
||||||
colors[color_count].g = (color_val >> 8) & 0xFF;
|
|
||||||
colors[color_count].b = color_val & 0xFF;
|
|
||||||
color_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (color_count < 4) {
|
|
||||||
color_part = colors_str.substring(last_comma + 1);
|
|
||||||
if (color_part.length() > 0) {
|
|
||||||
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
|
|
||||||
colors[color_count].r = (color_val >> 16) & 0xFF;
|
|
||||||
colors[color_count].g = (color_val >> 8) & 0xFF;
|
|
||||||
colors[color_count].b = color_val & 0xFF;
|
|
||||||
color_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color_count > 0) {
|
|
||||||
int text_len = text_str.length();
|
|
||||||
int part_len = text_len / color_count;
|
|
||||||
for (int i = 0; i < color_count; i++) {
|
|
||||||
colors[i].start_index = i * part_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyMultiColorTextNodeByGlobalId(global_id, text, colors, color_count);
|
|
||||||
server.send(200, "text/plain", "OK");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
server.send(400, "text/plain", "Invalid arguments for /modify-multicolor-text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleGetNodes()
|
|
||||||
{
|
|
||||||
StaticJsonDocument<2048> jsonDoc;
|
|
||||||
JsonArray nodes = jsonDoc.to<JsonArray>();
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
||||||
{
|
|
||||||
if (text_nodes[i].disappear_time != 0)
|
|
||||||
{
|
|
||||||
JsonObject node = nodes.createNestedObject();
|
|
||||||
node["global_id"] = text_nodes[i].global_id;
|
|
||||||
node["text"] = text_nodes[i].content;
|
|
||||||
node["color"] = text_nodes[i].color;
|
|
||||||
node["x"] = text_nodes[i].pos_x;
|
|
||||||
node["y"] = text_nodes[i].pos_y;
|
|
||||||
node["slowness"] = text_nodes[i].scroll_slowness;
|
|
||||||
node["is_repeating"] = text_nodes[i].is_repeating;
|
|
||||||
node["type"] = "text";
|
|
||||||
node["slowness"] = text_nodes[i].scroll_slowness;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
||||||
{
|
|
||||||
if (multi_color_text_node[i].disappear_time != 0)
|
|
||||||
{
|
|
||||||
JsonObject node = nodes.createNestedObject();
|
|
||||||
node["global_id"] = multi_color_text_node[i].global_id;
|
|
||||||
node["text"] = multi_color_text_node[i].content;
|
|
||||||
JsonArray colors = node.createNestedArray("colors");
|
|
||||||
for(int j = 0; j < multi_color_text_node[i].color_count; j++)
|
|
||||||
{
|
|
||||||
JsonObject color = colors.createNestedObject();
|
|
||||||
color["r"] = multi_color_text_node[i].colors[j].r;
|
|
||||||
color["g"] = multi_color_text_node[i].colors[j].g;
|
|
||||||
color["b"] = multi_color_text_node[i].colors[j].b;
|
|
||||||
}
|
|
||||||
node["x"] = multi_color_text_node[i].pos_x;
|
|
||||||
node["y"] = multi_color_text_node[i].pos_y;
|
|
||||||
node["slowness"] = multi_color_text_node[i].scroll_slowness;
|
|
||||||
node["is_repeating"] = multi_color_text_node[i].is_repeating;
|
|
||||||
node["type"] = "multi-color";
|
|
||||||
node["slowness"] = multi_color_text_node[i].scroll_slowness;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String jsonString;
|
|
||||||
serializeJson(jsonDoc, jsonString);
|
|
||||||
server.send(200, "application/json", jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_server()
|
|
||||||
{
|
|
||||||
WiFi.begin(ssid, password);
|
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
|
||||||
pixels.clear();
|
pixels.clear();
|
||||||
scrollAllScrollableTexts(true);
|
scrollAllScrollableTexts(true);
|
||||||
pixels.show();
|
pixels.show();
|
||||||
@@ -356,20 +75,9 @@ void handleMulticolorText() {
|
|||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
server.on("/", handleRoot);
|
server.on("/", handleRoot);
|
||||||
server.on("/nodes", HTTP_GET, handleGetNodes);
|
server.on("/toggle-program", HTTP_POST, handleToggleProgram);
|
||||||
server.on("/text", HTTP_POST, handleText);
|
server.on("/program-status", HTTP_GET, handleProgramStatus);
|
||||||
server.on("/modify-text", HTTP_POST, handleModifyText);
|
|
||||||
server.on("/multicolor-text", HTTP_POST, handleMulticolorText);
|
|
||||||
server.on("/modify-multicolor-text", HTTP_POST, handleModifyMultiColorText);
|
|
||||||
server.on("/brightness", HTTP_POST, handleBrightness);
|
server.on("/brightness", HTTP_POST, handleBrightness);
|
||||||
server.on("/upload-page", HTTP_GET, handleUploadPage);
|
|
||||||
server.on("/upload-bmp", HTTP_POST, []() {
|
|
||||||
if (upload_error_message.length() > 0) {
|
|
||||||
server.send(400, "text/plain", upload_error_message);
|
|
||||||
} else {
|
|
||||||
server.send(200, "text/plain", "Upload OK");
|
|
||||||
}
|
|
||||||
}, handleBmpUpload);
|
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user