Update ESP82_PANEL.ino

This commit is contained in:
2025-07-05 13:10:16 +00:00
parent 46ddc8425f
commit aba98c4924

View File

@ -6,36 +6,75 @@
#include <ArduinoJson.h>
#include <fonts/Font_BOLD.h>
#include "data/output.h"
#include "data/bear.h"
#include "data/kitleft.h"
const uint8_t* FONT = Font_BOLD;
// Константы
const uint8_t *FONT = Font_BOLD;
const int WIDTH = 4; // Количество матриц в ширину
const int HEIGHT = 3; // Количество матриц в высоту
const int DISPLAY_WIDTH = WIDTH * 32;
const int DISPLAY_HEIGHT = HEIGHT * 16;
const int TEXT_Y_OFFSET = 17;
const int SCROLL_DELAY = 5; // Задержка для скролла в секундах
const int SCROLL_SPEED = 30; // Скорость скролла в мс
const int TEXT_HEIGHT = 16; // Высота области для текста
const int TOP_LOGO_HEIGHT = 16; // Высота верхнего логотипа
const int BOTTOM_LOGO_HEIGHT = 16; // Высота нижнего логотипа
// Настройки WiFi
const char *SSID = "SSID";
const char *PASSWORD = "PASSWORD";
AsyncWebServer server(80);
String displayText = "Привет из СКБ \"КИТ\"";
bool isON = true;
bool showText = true;
bool scroll = true;
const char* skb = "СКБ";
const char* kit = "\"КИТ\"";
const char* ictib = "ИКТИБ";
const char* ssid = "SKBKIT";
const char* password = "skbkit2024";
IPAddress staticIP(10, 131, 170, 4);
IPAddress gateway(10, 131, 170, 1);
IPAddress subnet(255, 255, 255, 0);
// Текстовые константы
const char SKB[] PROGMEM = "СКБ";
const char KIT[] PROGMEM = "\"КИТ\"";
const char ICTIB[] PROGMEM = "ИКТИБ";
// Перечисления для состояний
enum class PanelState : int {
STATIC_LOGO = 0,
SCROLLING_TEXT = 1,
ANIMATED_LOGO = 2,
BEAR = 3,
UNKNOWN = -1
};
enum class PanelPower : int {
POWER_OFF = 0,
POWER_ON = 1
};
// Глобальные переменные
AsyncWebServer server(80);
String displayText = "СКБ~\"КИТ\"";
PanelState currentState = PanelState::ANIMATED_LOGO;
PanelPower panelPower = PanelPower::POWER_ON;
bool showText = true;
bool isUpdater = false;
bool scrollEnabled = false;
int scrollPosition = 0;
unsigned long lastScrollTime = 0;
SPIDMD dmd(WIDTH, HEIGHT);
DMD_TextBox* box = nullptr; // Указатель на текстовый бокс
Ticker ticker;
DMD_TextBox *box = nullptr;
Ticker modeTicker;
Ticker scrollTicker;
void setupWiFi() {
WiFi.begin(ssid, password);
Serial.print("Подключение к WiFi ");
Serial.println(SSID);
WiFi.begin(SSID, PASSWORD);
if (!WiFi.config(staticIP, gateway, subnet)) {
Serial.println("Failed WiFi connect!");
Serial.println("Ошибка настройки статического IP!");
} else {
Serial.println("Static WiFi configured!");
Serial.println("Статический IP настроен");
}
while (WiFi.status() != WL_CONNECTED) {
@ -43,122 +82,110 @@ void setupWiFi() {
Serial.print(".");
}
Serial.println("\nConnected to WiFi");
Serial.println("\nПодключено к WiFi");
Serial.print("IP адрес: ");
Serial.println(WiFi.localIP());
// WiFi.forceSleepWake();
}
void drawBinaryArrayKit() {
void drawBinaryArray(const uint8_t *data) {
dmd.clearScreen();
for (int x = 0; x < WIDTH * 32; x++) {
for (int y = 0; y < HEIGHT * 16; y++) {
int idx = y * (WIDTH * 32) + x; // Индекс с учетом полной ширины
if (output[idx] == 1) {
dmd.setPixel(x, y, GRAPHICS_ON);
} else {
dmd.setPixel(x, y, GRAPHICS_OFF);
}
}
}
}
void drawBinaryArrayKitLeft() {
dmd.clearScreen();
for (int x = 0; x < WIDTH * 32; x++) {
for (int y = 0; y < HEIGHT * 16; y++) {
int idx = y * (WIDTH * 32) + x; // Индекс с учетом полной ширины
if (kitleft[idx] == 1) {
dmd.setPixel(x, y, GRAPHICS_ON);
} else {
dmd.setPixel(x, y, GRAPHICS_OFF);
}
for (int y = 0; y < DISPLAY_HEIGHT; y++) {
for (int x = 0; x < DISPLAY_WIDTH; x++) {
uint8_t pixel = pgm_read_byte(&data[y * DISPLAY_WIDTH + x]);
dmd.setPixel(x, y, pixel ? GRAPHICS_ON : GRAPHICS_OFF);
}
}
}
void switchDisplayMode() {
if (isON) {
if (panelPower == PanelPower::POWER_ON) {
if (showText) {
drawScrollingText(); // Запускаем скролл текста на второй строке
drawScrollingText();
} else {
drawBinaryArrayKit();
drawBinaryArray(output);
}
showText = !showText;
}
}
void switchDisplayModeScroll() {
if (isON && scroll) {
//box->scrollX(1); // Двигаем текст влево
//box->drawScrollingText();
dmd.marqueeScrollX(1);
}
}
void drawScrollingText() {
if (!box) {
box = new DMD_TextBox(dmd, 0, 17, WIDTH * 32, HEIGHT * 16); // Устанавливаем текстовый бокс на вторую строку
} else {
box->clear();
if(box){
delete box;
box = nullptr;
}
// Вычисляем ширину текста
int textWidth = dmd.stringWidth(displayText.c_str());
box->print(displayText.c_str());
}
// Определяем, нужен ли скроллинг
scrollEnabled = (textWidth > DISPLAY_WIDTH);
void drawText() {
// Очищаем экран
dmd.clearScreen();
if (!box) { // Создаём текстовый бокс только один раз
box = new DMD_TextBox(dmd, 0, 0, WIDTH * 32, HEIGHT * 16);
} else {
box->clear(); // Очищаем текущий бокс перед обновлением текста
}
// Рисуем статические элементы
drawKitLogoTop();
drawKitLogoBottom();
// Устанавливаем начальную позицию
scrollPosition = scrollEnabled ? DISPLAY_WIDTH : (DISPLAY_WIDTH - textWidth) / 2;
// Рисуем начальное положение текста
if (box) {
delete box;
}
box = new DMD_TextBox(dmd, scrollPosition, TEXT_Y_OFFSET, DISPLAY_WIDTH, TEXT_HEIGHT);
box->print(displayText.c_str());
// Управляем тикером
scrollTicker.detach();
if (scrollEnabled) {
scrollTicker.attach_ms(SCROLL_SPEED, handleScroll);
} else {
scrollTicker.detach();
}
}
void handleText(AsyncWebServerRequest* request, const JsonVariant& json) {
if (!json.containsKey("text")) {
request->send(400, "application/json", "{\"error\": \"Missing text field\"}");
void handleScroll() {
if (!scrollEnabled || panelPower != PanelPower::POWER_ON || currentState != PanelState::SCROLLING_TEXT) {
return;
}
displayText = json["text"].as<String>();
drawText();
unsigned long currentTime = millis();
if (currentTime - lastScrollTime < SCROLL_SPEED) {
return;
}
lastScrollTime = currentTime;
request->send(200, "application/json", "{\"status\": \"OK\"}");
// Вычисляем ширину текста
int textWidth = dmd.stringWidth(displayText.c_str());
// Обновляем позицию
scrollPosition--;
// Проверяем, нужно ли перезапустить скроллинг
if (scrollPosition < -textWidth) {
scrollPosition = DISPLAY_WIDTH;
}
// Очищаем только текстовую область
for (int y = TEXT_Y_OFFSET; y < TEXT_Y_OFFSET + TEXT_HEIGHT; y++) {
for (int x = 0; x < DISPLAY_WIDTH; x++) {
dmd.setPixel(x, y, GRAPHICS_OFF);
}
}
// Рисуем текст с помощью DMD_TextBox
if (box) {
delete box;
}
box = new DMD_TextBox(dmd, scrollPosition, TEXT_Y_OFFSET, DISPLAY_WIDTH, TEXT_HEIGHT);
box->print(displayText.c_str());
}
void handleTurn(AsyncWebServerRequest* request, const JsonVariant& json) {
String panelTurn = json["panel"].as<String>();
String panelState = json["state"].as<String>();
if (json.containsKey("panel")) {
panelTurn = json["panel"].as<String>();
}
if (json.containsKey("state")) {
panelState = json["state"].as<String>();
}
ticker.detach();
if (!panelTurn.isEmpty()) {
if (panelTurn == "on") {
isON = true;
} else if (panelTurn == "off") {
isON = false;
dmd.clearScreen();
dmd.drawFilledBox(0, 0, WIDTH * 32 - 1, HEIGHT * 16 - 1, GRAPHICS_OFF);
}
}
if (!panelState.isEmpty()) {
if (panelState == "1") { // Скролинг текста
dmd.clearScreen();
dmd.selectFont(FONT);
void drawKitLogoTop() {
dmd.drawLine(0, 14, 16, 14, GRAPHICS_ON); // x y x y
dmd.drawLine(0, 13, 16, 13, GRAPHICS_ON);
@ -169,40 +196,40 @@ void handleTurn(AsyncWebServerRequest* request, const JsonVariant& json) {
dmd.drawLine(20, 4, 30, 4, GRAPHICS_ON);
dmd.drawCircle(33, 4, 3, GRAPHICS_ON);
dmd.drawCircle(33, 4, 2, GRAPHICS_ON); //1 line
dmd.drawCircle(33, 4, 2, GRAPHICS_ON); // 1 line
dmd.drawLine(20, 14, 40, 14, GRAPHICS_ON);
dmd.drawLine(20, 13, 40, 13, GRAPHICS_ON);
dmd.drawLine(20, 14, 50, 14, GRAPHICS_ON);
dmd.drawLine(20, 13, 50, 13, GRAPHICS_ON);
dmd.drawLine(40, 14, 45, 5, GRAPHICS_ON);
dmd.drawLine(40, 13, 45, 4, GRAPHICS_ON);
dmd.drawLine(50, 14, 55, 5, GRAPHICS_ON);
dmd.drawLine(50, 13, 55, 4, GRAPHICS_ON);
dmd.drawLine(45, 5, 60, 5, GRAPHICS_ON);
dmd.drawLine(45, 4, 60, 4, GRAPHICS_ON);
dmd.drawLine(55, 5, 70, 5, GRAPHICS_ON);
dmd.drawLine(55, 4, 70, 4, GRAPHICS_ON);
dmd.drawLine(60, 5, 65, 14, GRAPHICS_ON);
dmd.drawLine(60, 4, 65, 13, GRAPHICS_ON);
dmd.drawLine(70, 5, 75, 14, GRAPHICS_ON);
dmd.drawLine(70, 4, 75, 13, GRAPHICS_ON);
dmd.drawLine(65, 14, 75, 14, GRAPHICS_ON);
dmd.drawLine(65, 13, 75, 13, GRAPHICS_ON);
dmd.drawCircle(78, 13, 3, GRAPHICS_ON);
dmd.drawCircle(78, 13, 2, GRAPHICS_ON); //2 line
dmd.drawLine(75, 14, 85, 14, GRAPHICS_ON);
dmd.drawLine(75, 13, 85, 13, GRAPHICS_ON);
dmd.drawCircle(88, 13, 3, GRAPHICS_ON);
dmd.drawCircle(88, 13, 2, GRAPHICS_ON); // 2 line
dmd.drawCircle(68, 4, 3, GRAPHICS_ON);
dmd.drawCircle(68, 4, 2, GRAPHICS_ON);
dmd.drawLine(71, 5, 85, 5, GRAPHICS_ON);
dmd.drawLine(71, 4, 85, 4, GRAPHICS_ON);
dmd.drawLine(85, 5, 90, 14, GRAPHICS_ON);
dmd.drawLine(85, 4, 90, 13, GRAPHICS_ON);
dmd.drawLine(90, 14, 100, 14, GRAPHICS_ON);
dmd.drawLine(90, 13, 100, 13, GRAPHICS_ON);
dmd.drawLine(100, 14, 105, 5, GRAPHICS_ON);
dmd.drawLine(100, 13, 105, 4, GRAPHICS_ON);
dmd.drawLine(105, 5, 128, 5, GRAPHICS_ON);
dmd.drawLine(105, 4, 128, 4, GRAPHICS_ON); // 3 line
// ======================================
dmd.drawCircle(78, 4, 3, GRAPHICS_ON);
dmd.drawCircle(78, 4, 2, GRAPHICS_ON);
dmd.drawLine(81, 5, 95, 5, GRAPHICS_ON);
dmd.drawLine(81, 4, 95, 4, GRAPHICS_ON);
dmd.drawLine(95, 5, 100, 14, GRAPHICS_ON);
dmd.drawLine(95, 4, 100, 13, GRAPHICS_ON);
dmd.drawLine(100, 14, 110, 14, GRAPHICS_ON);
dmd.drawLine(100, 13, 110, 13, GRAPHICS_ON);
dmd.drawLine(110, 14, 115, 5, GRAPHICS_ON);
dmd.drawLine(110, 13, 115, 4, GRAPHICS_ON);
dmd.drawLine(115, 5, 128, 5, GRAPHICS_ON);
dmd.drawLine(115, 4, 128, 4, GRAPHICS_ON); // 3 line
}
void drawKitLogoBottom() {
dmd.drawLine(0, 35, 16, 35, GRAPHICS_ON);
dmd.drawLine(0, 36, 16, 36, GRAPHICS_ON);
dmd.drawLine(16, 35, 21, 43, GRAPHICS_ON);
@ -212,56 +239,207 @@ void handleTurn(AsyncWebServerRequest* request, const JsonVariant& json) {
dmd.drawCircle(34, 44, 3, GRAPHICS_ON);
dmd.drawCircle(34, 44, 2, GRAPHICS_ON); // 1 line
dmd.drawLine(20, 35, 40, 35, GRAPHICS_ON);
dmd.drawLine(20, 36, 40, 36, GRAPHICS_ON);
dmd.drawLine(40, 35, 45, 43, GRAPHICS_ON);
dmd.drawLine(40, 36, 45, 44, GRAPHICS_ON); // 2 line
dmd.drawLine(20, 35, 50, 35, GRAPHICS_ON);
dmd.drawLine(20, 36, 50, 36, GRAPHICS_ON);
dmd.drawLine(50, 35, 55, 43, GRAPHICS_ON);
dmd.drawLine(50, 36, 55, 44, GRAPHICS_ON); // 2 line
dmd.drawLine(45, 43, 60, 43, GRAPHICS_ON);
dmd.drawLine(45, 44, 60, 44, GRAPHICS_ON);
dmd.drawLine(55, 43, 70, 43, GRAPHICS_ON);
dmd.drawLine(55, 44, 70, 44, GRAPHICS_ON);
dmd.drawLine(60, 43, 65, 35, GRAPHICS_ON);
dmd.drawLine(60, 44, 65, 36, GRAPHICS_ON);
dmd.drawLine(65, 35, 75, 35, GRAPHICS_ON);
dmd.drawLine(65, 36, 75, 36, GRAPHICS_ON);
dmd.drawCircle(78, 36, 3, GRAPHICS_ON);
dmd.drawCircle(78, 36, 2, GRAPHICS_ON);
} else if (panelState == "2") { // Текст и картинка динамика
dmd.drawLine(70, 43, 75, 35, GRAPHICS_ON);
dmd.drawLine(70, 44, 75, 36, GRAPHICS_ON);
dmd.drawLine(75, 35, 85, 35, GRAPHICS_ON);
dmd.drawLine(75, 36, 85, 36, GRAPHICS_ON);
dmd.drawCircle(88, 36, 3, GRAPHICS_ON);
dmd.drawCircle(88, 36, 2, GRAPHICS_ON);
dmd.drawCircle(78, 44, 3, GRAPHICS_ON);
dmd.drawCircle(78, 44, 2, GRAPHICS_ON);
dmd.drawLine(81, 43, 95, 43, GRAPHICS_ON);
dmd.drawLine(81, 44, 95, 44, GRAPHICS_ON);
dmd.drawLine(95, 43, 100, 35, GRAPHICS_ON);
dmd.drawLine(95, 44, 100, 36, GRAPHICS_ON);
dmd.drawLine(100, 35, 110, 35, GRAPHICS_ON);
dmd.drawLine(100, 36, 110, 36, GRAPHICS_ON);
dmd.drawLine(110, 35, 115, 43, GRAPHICS_ON);
dmd.drawLine(110, 36, 115, 44, GRAPHICS_ON);
dmd.drawLine(115, 43, 128, 43, GRAPHICS_ON);
dmd.drawLine(115, 44, 128, 44, GRAPHICS_ON);
}
void drawBear(){
drawBinaryArray(bear);
}
void drawKitLeftLogoWithText() {
drawBinaryArray(kitleft);
dmd.drawString_P(79, 2, SKB);
dmd.drawString_P(71, 17, KIT);
dmd.drawString_P(68, 32, ICTIB);
}
void updateScreen() {
modeTicker.detach();
scrollTicker.detach();
if (panelPower == PanelPower::POWER_OFF) {
dmd.clearScreen();
return;
}
switch (currentState) {
case PanelState::SCROLLING_TEXT:
drawKitLogoTop(); // Статичный верхний логотип
drawScrollingText(); // Прокручиваемый текст
drawKitLogoBottom(); // Статичный нижний логотип
if (scrollEnabled) {
scrollTicker.attach_ms(SCROLL_SPEED, handleScroll);
}
break;
case PanelState::ANIMATED_LOGO:
dmd.clearScreen();
drawText();
ticker.attach(5, switchDisplayMode);
modeTicker.attach(SCROLL_DELAY, switchDisplayMode);
dmd.clearScreen();
drawBinaryArrayKit();
} else if (panelState == "3") { // Текст и картинка статика
drawBinaryArray(output);
break;
case PanelState::STATIC_LOGO:
dmd.clearScreen();
drawBinaryArrayKitLeft();
dmd.drawString_P(79, 2, skb);
dmd.drawString_P(71, 17, kit);
dmd.drawString_P(68, 32, ictib);
drawKitLeftLogoWithText();
break;
case PanelState::BEAR:
dmd.clearScreen();
drawBear();
break;
default:
dmd.clearScreen();
drawKitLeftLogoWithText();
break;
}
}
void drawText() {
int textWidth = dmd.stringWidth(displayText, Font_BOLD);
int xOffset = (textWidth < DISPLAY_WIDTH) ? (DISPLAY_WIDTH - textWidth) / 2 : 0;
if (box) {
delete box;
box = nullptr;
}
String response = "{\"panel\" : " + String(isON ? "\"on\"" : "\"off\"") + ", \"state\" : " + (panelState.isEmpty() ? "null" : panelState) + "}";
box = new DMD_TextBox(dmd, xOffset, TEXT_Y_OFFSET, DISPLAY_WIDTH, DISPLAY_HEIGHT);
box->print(displayText.c_str());
}
PanelState intToPanelState(int state) {
switch (state) {
case 0:
return PanelState::STATIC_LOGO;
case 1:
return PanelState::SCROLLING_TEXT;
case 2:
return PanelState::ANIMATED_LOGO;
case 3:
return PanelState::BEAR;
default:
return PanelState::UNKNOWN;
}
}
String panelStateToString(PanelState state) {
switch (state) {
case PanelState::STATIC_LOGO:
return "0";
case PanelState::SCROLLING_TEXT:
return "1";
case PanelState::ANIMATED_LOGO:
return "2";
case PanelState::BEAR:
return "3";
default:
return "-1";
}
}
void handleText(AsyncWebServerRequest *request, const JsonVariant &json) {
if (!json.containsKey("text")) {
request->send(400, "application/json", "{\"error\": \"Missing text field\"}");
return;
}
displayText = json["text"].as<String>();
isUpdater = true;
updateScreen();
request->send(200, "application/json", "{\"status\": \"OK\"}");
}
void handleTurn(AsyncWebServerRequest *request, const JsonVariant &json) {
bool powerChanged = false;
bool modeChanged = false;
// Обработка включения/выключения панели
if (json.containsKey("panel")) {
String panelTurn = json["panel"].as<String>();
PanelPower newPower;
if (panelTurn == "on") {
newPower = PanelPower::POWER_ON;
} else if (panelTurn == "off") {
newPower = PanelPower::POWER_OFF;
} else {
request->send(400, "application/json", "{\"error\": \"Invalid panel value, use 'on' or 'off'\"}");
return;
}
powerChanged = (panelPower != newPower);
panelPower = newPower;
}
// Обработка изменения режима
if (json.containsKey("state")) {
int panelStateInt = json["state"].as<int>();
PanelState newState = intToPanelState(panelStateInt);
if (newState == PanelState::UNKNOWN) {
request->send(400, "application/json", "{\"error\": \"Invalid state value\"}");
return;
}
modeChanged = (currentState != newState);
currentState = newState;
}
if (powerChanged || modeChanged) {
updateScreen();
}
String response = "{\"panel\":\"" + String(panelPower == PanelPower::POWER_ON ? "on" : "off") + "\",\"state\":" + panelStateToString(currentState) + "}";
request->send(200, "application/json", response);
}
void setup() {
Serial.begin(9600);
setupWiFi();
Serial.println("\n\nStarting server...");
dmd.setBrightness(255);
dmd.selectFont(FONT);
dmd.begin();
dmd.clearScreen();
box = new DMD_TextBox(dmd, 0, 0, WIDTH * 32, HEIGHT * 16); // Создаём текстовый бокс один раз
box->print("Hello");
panelPower = PanelPower::POWER_ON;
updateScreen();
server.on(
"/api/text", HTTP_POST,
[](AsyncWebServerRequest* request) {},
NULL,
[](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t, size_t) {
"/api/text", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t, size_t) {
StaticJsonDocument<256> jsonReq;
DeserializationError error = deserializeJson(jsonReq, data, len);
@ -270,20 +448,16 @@ void setup() {
return;
}
handleText(request, jsonReq.as<JsonVariant>()); // Передаём как const JsonVariant&
handleText(request, jsonReq.as<JsonVariant>());
});
server.on("/api/led", HTTP_GET, [](AsyncWebServerRequest* request) {
if (isON) {
request->send(200, "application/json", "{\"panel\" : \"on\"}");
} else if (!isON) {
request->send(200, "application/json", "{\"panel\" : \"off\"}");
}
server.on("/api/led", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "application/json",
String("{\"panel\":\"") + (panelPower == PanelPower::POWER_ON ? "on" : "off") + "\",\"state\":" + String(static_cast<int>(currentState)) + "}");
});
server.on(
"/api/led", HTTP_POST, [](AsyncWebServerRequest* request) {},
NULL, [](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t, size_t) {
"/api/led", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t, size_t) {
StaticJsonDocument<256> jsonReq;
DeserializationError error = deserializeJson(jsonReq, data, len);
@ -299,4 +473,8 @@ void setup() {
}
void loop() {
if (isUpdater) {
isUpdater = false;
updateScreen();
}
}