457 lines
14 KiB
Arduino
457 lines
14 KiB
Arduino
#include <Adafruit_NeoPixel.h>
|
|
#include <WiFi.h>
|
|
#include <WebServer.h>
|
|
#include <ArduinoJson.h>
|
|
#include "config.h"
|
|
#include "index.h"
|
|
#include "fonts.h"
|
|
#include "lowLevel.h"
|
|
#include "upload_page.h"
|
|
#include "prototypes.h"
|
|
|
|
#ifdef __AVR__
|
|
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
|
|
#endif
|
|
|
|
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
|
|
|
|
TextNode text_nodes[MAX_TEXT_NODES_COUNT];
|
|
MultiColorTextNode multi_color_text_node[MAX_TEXT_NODES_COUNT];
|
|
Cursor cursor;
|
|
unsigned char global_node_id = 0;
|
|
|
|
bool main_animation_started = false;
|
|
unsigned char bottom_text_state = 0; // 0 = "Technik informatyk", 1 = "technik programista"
|
|
unsigned char scroll_node_global_id = 0; // tracks current bottom-row node
|
|
bool scroll_node_active = false;
|
|
|
|
Image saved_images[MAX_IMAGES_SAVED] = {
|
|
{},{}
|
|
};
|
|
|
|
|
|
short getTextNodeY2(TextNode *node)
|
|
{
|
|
return node->pos_y + node->characterSize.height - 1;
|
|
}
|
|
|
|
short getTextNodeX2(TextNode *node)
|
|
{
|
|
if (node->character_count == 0) return node->pos_x;
|
|
return node->pos_x + (node->characterSize.width * node->character_count) + (node->character_count - 1) - 1;
|
|
}
|
|
|
|
TextNode* addNewTextNode
|
|
(
|
|
char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor, short pos_x, short pos_y,
|
|
unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating
|
|
)
|
|
{
|
|
unsigned char text_length = strlen(text);
|
|
if (text_length == 0){return nullptr;}
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (text_nodes[i].disappear_time == 0)
|
|
{
|
|
strncpy(text_nodes[i].content, text, TEXT_MAX_LENGTH);
|
|
text_nodes[i].color = color;
|
|
|
|
if (handle_pos_via_cursor)
|
|
{
|
|
text_nodes[i].pos_x = cursor.x;
|
|
text_nodes[i].pos_y = cursor.y;
|
|
}
|
|
else
|
|
{
|
|
text_nodes[i].pos_x = pos_x;
|
|
text_nodes[i].pos_y = pos_y;
|
|
}
|
|
|
|
text_nodes[i].characterSize.height = is_small ? SMALL_TEXT_HEIGHT : MEDIUM_TEXT_HEIGHT;
|
|
text_nodes[i].characterSize.width = is_small ? SMALL_TEXT_WIDTH : MEDIUM_TEXT_WIDTH;
|
|
text_nodes[i].character_count = text_length;
|
|
text_nodes[i].scroll_slowness = scroll_slowness;
|
|
text_nodes[i].is_scrolling = is_scrolling;
|
|
text_nodes[i].is_repeating = is_repeating;
|
|
text_nodes[i].disappear_time = disappear_time;
|
|
text_nodes[i].global_id = global_node_id;
|
|
global_node_id++;
|
|
|
|
if (handle_pos_via_cursor)
|
|
{
|
|
cursor.x += (text_nodes[i].characterSize.width + 1) * text_length;
|
|
}
|
|
return &text_nodes[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void modifyTextNodeByGlobalId(unsigned char global_id, uint32_t new_color, char new_text[TEXT_MAX_LENGTH + 1], unsigned char new_slowness)
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (text_nodes[i].global_id == global_id && text_nodes[i].disappear_time != 0)
|
|
{
|
|
text_nodes[i].color = new_color;
|
|
strncpy(text_nodes[i].content, new_text, TEXT_MAX_LENGTH);
|
|
text_nodes[i].character_count = strlen(new_text);
|
|
text_nodes[i].scroll_slowness = new_slowness;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void modifyMultiColorTextNodeByGlobalId(unsigned char global_id, char new_text[TEXT_MAX_LENGTH + 1], RGBWithIndex new_colors[4], unsigned char new_color_count)
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (multi_color_text_node[i].global_id == global_id && multi_color_text_node[i].disappear_time != 0)
|
|
{
|
|
strncpy(multi_color_text_node[i].content, new_text, TEXT_MAX_LENGTH);
|
|
multi_color_text_node[i].character_count = strlen(new_text);
|
|
multi_color_text_node[i].color_count = new_color_count;
|
|
for(int j = 0; j < new_color_count; j++)
|
|
{
|
|
multi_color_text_node[i].colors[j] = new_colors[j];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void scrollAllScrollableTexts(bool split_scroll_mode = false)
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (text_nodes[i].disappear_time == 0) {continue;}
|
|
|
|
if (text_nodes[i].is_scrolling)
|
|
{
|
|
if (text_nodes[i].scroll_slowness > text_nodes[i].scroll_progress)
|
|
{
|
|
text_nodes[i].scroll_progress++;
|
|
}
|
|
else
|
|
{
|
|
text_nodes[i].scroll_progress = 0;
|
|
|
|
short x1 = text_nodes[i].pos_x;
|
|
short x2 = getTextNodeX2(&text_nodes[i]);
|
|
if (split_scroll_mode || text_nodes[i].pos_y < 7)
|
|
{
|
|
if (x2 < 0)
|
|
{
|
|
if (text_nodes[i].is_repeating)
|
|
{
|
|
text_nodes[i].pos_x = DISPLAY_MAX_X;
|
|
}
|
|
else
|
|
{
|
|
text_nodes[i].disappear_time = 1;
|
|
}
|
|
continue;
|
|
}
|
|
text_nodes[i].pos_x--;
|
|
}
|
|
else
|
|
{
|
|
if (x1 > DISPLAY_MAX_X)
|
|
{
|
|
if (text_nodes[i].is_repeating)
|
|
{
|
|
text_nodes[i].pos_x = -getTextNodeX2(&text_nodes[i]) + text_nodes[i].pos_x;
|
|
}
|
|
else
|
|
{
|
|
text_nodes[i].disappear_time = 1;
|
|
}
|
|
continue;
|
|
}
|
|
text_nodes[i].pos_x++;
|
|
}
|
|
}
|
|
}
|
|
|
|
cursor.x = text_nodes[i].pos_x;
|
|
cursor.y = text_nodes[i].pos_y;
|
|
|
|
for (unsigned char j = 0; j < text_nodes[i].character_count; j++)
|
|
{
|
|
char ch = text_nodes[i].content[j];
|
|
if (ch < '!' || ch > '~')
|
|
{
|
|
ch = ' ';
|
|
}
|
|
drawCharacter(font7x5[ch - ' '], text_nodes[i].characterSize.height, text_nodes[i].characterSize.width, text_nodes[i].color, &cursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
MultiColorTextNode* addNewMultiColor
|
|
(
|
|
char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor, short pos_x, short pos_y,
|
|
unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating
|
|
)
|
|
{
|
|
unsigned char text_length = strlen(text);
|
|
if (text_length == 0){return nullptr;}
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (multi_color_text_node[i].disappear_time == 0)
|
|
{
|
|
strncpy(multi_color_text_node[i].content, text, TEXT_MAX_LENGTH);
|
|
multi_color_text_node[i].color_count = color_count;
|
|
for(int j = 0; j < color_count; j++)
|
|
{
|
|
multi_color_text_node[i].colors[j] = colors[j];
|
|
}
|
|
|
|
if (handle_pos_via_cursor)
|
|
{
|
|
multi_color_text_node[i].pos_x = cursor.x;
|
|
multi_color_text_node[i].pos_y = cursor.y;
|
|
}
|
|
else
|
|
{
|
|
multi_color_text_node[i].pos_x = pos_x;
|
|
multi_color_text_node[i].pos_y = pos_y;
|
|
}
|
|
|
|
multi_color_text_node[i].characterSize.height = is_small ? SMALL_TEXT_HEIGHT : MEDIUM_TEXT_HEIGHT;
|
|
multi_color_text_node[i].characterSize.width = is_small ? SMALL_TEXT_WIDTH : MEDIUM_TEXT_WIDTH;
|
|
multi_color_text_node[i].character_count = text_length;
|
|
multi_color_text_node[i].scroll_slowness = scroll_slowness;
|
|
multi_color_text_node[i].is_scrolling = is_scrolling;
|
|
multi_color_text_node[i].is_repeating = is_repeating;
|
|
multi_color_text_node[i].disappear_time = disappear_time;
|
|
multi_color_text_node[i].global_id = global_node_id;
|
|
global_node_id++;
|
|
|
|
if (handle_pos_via_cursor)
|
|
{
|
|
cursor.x += (multi_color_text_node[i].characterSize.width + 1) * text_length;
|
|
}
|
|
return &multi_color_text_node[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
short getMultiColorTextNodeX2(MultiColorTextNode *node)
|
|
{
|
|
if (node->character_count == 0) return node->pos_x;
|
|
return node->pos_x + (node->characterSize.width * node->character_count) + (node->character_count - 1) - 1;
|
|
}
|
|
|
|
void scrollAllMultiColorTexts(bool split_scroll_mode = false)
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (multi_color_text_node[i].disappear_time == 0) {continue;}
|
|
|
|
if (multi_color_text_node[i].is_scrolling)
|
|
{
|
|
if (multi_color_text_node[i].scroll_slowness > multi_color_text_node[i].scroll_progress)
|
|
{
|
|
multi_color_text_node[i].scroll_progress++;
|
|
}
|
|
else
|
|
{
|
|
multi_color_text_node[i].scroll_progress = 0;
|
|
|
|
short x1 = multi_color_text_node[i].pos_x;
|
|
short x2 = getMultiColorTextNodeX2(&multi_color_text_node[i]);
|
|
if (split_scroll_mode || multi_color_text_node[i].pos_y < 7)
|
|
{
|
|
if (x2 < 0)
|
|
{
|
|
if (multi_color_text_node[i].is_repeating)
|
|
{
|
|
multi_color_text_node[i].pos_x = DISPLAY_MAX_X;
|
|
}
|
|
else
|
|
{
|
|
multi_color_text_node[i].disappear_time = 1;
|
|
}
|
|
continue;
|
|
}
|
|
multi_color_text_node[i].pos_x--;
|
|
}
|
|
else
|
|
{
|
|
if (x1 > DISPLAY_MAX_X)
|
|
{
|
|
if (multi_color_text_node[i].is_repeating)
|
|
{
|
|
multi_color_text_node[i].pos_x = -getMultiColorTextNodeX2(&multi_color_text_node[i]) + multi_color_text_node[i].pos_x;
|
|
}
|
|
else
|
|
{
|
|
multi_color_text_node[i].disappear_time = 1;
|
|
}
|
|
continue;
|
|
}
|
|
multi_color_text_node[i].pos_x++;
|
|
}
|
|
}
|
|
}
|
|
|
|
cursor.x = multi_color_text_node[i].pos_x;
|
|
cursor.y = multi_color_text_node[i].pos_y;
|
|
|
|
uint32_t current_color = pixels.Color(multi_color_text_node[i].colors[0].r, multi_color_text_node[i].colors[0].g, multi_color_text_node[i].colors[0].b);
|
|
unsigned char color_index = 0;
|
|
|
|
for (unsigned char j = 0; j < multi_color_text_node[i].character_count; j++)
|
|
{
|
|
if (color_index < multi_color_text_node[i].color_count - 1 && multi_color_text_node[i].colors[color_index+1].start_index == j)
|
|
{
|
|
color_index++;
|
|
current_color = pixels.Color(multi_color_text_node[i].colors[color_index].r, multi_color_text_node[i].colors[color_index].g, multi_color_text_node[i].colors[color_index].b);
|
|
}
|
|
char ch = multi_color_text_node[i].content[j];
|
|
if (ch < '!' || ch > '~')
|
|
{
|
|
ch = ' ';
|
|
}
|
|
drawCharacter(font7x5[ch - ' '], multi_color_text_node[i].characterSize.height, multi_color_text_node[i].characterSize.width, current_color, &cursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawImageFromMemoryByIndex(unsigned char image_index, short pos_x, short pos_y, unsigned char brightness)
|
|
{
|
|
Image* img = &saved_images[image_index];
|
|
|
|
auto dimBy = [brightness](unsigned char color)
|
|
{
|
|
return color * brightness / 100;
|
|
};
|
|
|
|
for (unsigned char y = 0; y < img->height; y++)
|
|
{
|
|
for (unsigned char x = 0; x < img->width; x++)
|
|
{
|
|
setPixel(x + pos_x, y + pos_y, pixels.Color(dimBy(img->pixels[y][x].r), dimBy(img->pixels[y][x].g), dimBy(img->pixels[y][x].b)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleDisappearTimers()
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (text_nodes[i].disappear_time > 0)
|
|
{
|
|
text_nodes[i].disappear_time--;
|
|
if (text_nodes[i].disappear_time == 0)
|
|
{
|
|
fillPixels(text_nodes[i].pos_x, text_nodes[i].pos_y, getTextNodeX2(&text_nodes[i]), getTextNodeY2(&text_nodes[i]), 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleMultiColorDisappearTimers()
|
|
{
|
|
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--;
|
|
if (multi_color_text_node[i].disappear_time == 0)
|
|
{
|
|
fillPixels(multi_color_text_node[i].pos_x, multi_color_text_node[i].pos_y, getMultiColorTextNodeX2(&multi_color_text_node[i]), multi_color_text_node[i].pos_y + multi_color_text_node[i].characterSize.height - 1, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isNodeExistingByGlobal(unsigned char global_id)
|
|
{
|
|
for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++)
|
|
{
|
|
if (text_nodes[i].global_id == global_id && text_nodes[i].disappear_time != 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (multi_color_text_node[i].global_id == global_id && multi_color_text_node[i].disappear_time != 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void resetGlobalIdToAfterIntroValue()
|
|
{
|
|
global_node_id = 2;
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
Serial.println("----------------------------------------------------");
|
|
|
|
pixels.begin();
|
|
pixels.clear();
|
|
|
|
addNewTextNode("NO", 0xFF050505, false, 0, 0, 1, true, true);
|
|
addNewTextNode("INTERNET", 0xFF050505, false, 0, 9, 1, true, true);
|
|
|
|
scrollAllScrollableTexts(true);
|
|
pixels.show();
|
|
start_server();
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
if (!main_animation_started)
|
|
{
|
|
if (!isNodeExistingByGlobal(0) && !isNodeExistingByGlobal(1))
|
|
{
|
|
main_animation_started = true;
|
|
|
|
// Top row: "Witamy w PTI" — slow scroll, repeating, white + blue PTI
|
|
addNewMultiColor("Witamy w PTI",
|
|
(RGBWithIndex[4]){
|
|
{40, 40, 40, 0}, // white (dimmed) from char 0
|
|
{0, 0, 50, 9} // blue (dimmed) from char 9 ("PTI")
|
|
}, 2, false, 0, 0, 3, true, true, -1, true);
|
|
}
|
|
}
|
|
|
|
// Bottom row: alternating scrolling texts
|
|
if (main_animation_started &&
|
|
(!scroll_node_active || !isNodeExistingByGlobal(scroll_node_global_id)))
|
|
{
|
|
TextNode* node = nullptr;
|
|
if (bottom_text_state == 0)
|
|
{
|
|
node = addNewTextNode("Technik informatyk",
|
|
pixels.Color(0, 0, 50), // blue (dimmed)
|
|
false, DISPLAY_MAX_X, 9, 1, true, true, -1, false);
|
|
}
|
|
else
|
|
{
|
|
node = addNewTextNode("technik programista",
|
|
pixels.Color(50, 40, 0), // gold (dimmed)
|
|
false, DISPLAY_MAX_X, 9, 1, true, true, -1, false);
|
|
}
|
|
if (node != nullptr)
|
|
{
|
|
scroll_node_global_id = node->global_id;
|
|
scroll_node_active = true;
|
|
bottom_text_state = (bottom_text_state + 1) % 2;
|
|
}
|
|
}
|
|
|
|
pixels.clear();
|
|
handle_server();
|
|
handleDisappearTimers();
|
|
handleMultiColorDisappearTimers();
|
|
scrollAllScrollableTexts(true);
|
|
scrollAllMultiColorTexts(true);
|
|
pixels.show();
|
|
} |