Compare commits

..

22 Commits

Author SHA1 Message Date
Aleksei a40d324ab6 feat: 5 слотов для пультов вместо одного, v1.9 2026-06-17 13:28:26 +00:00
Aleksei 056f4030e1 fix: объявление cfg_peer_ip 2026-06-17 11:49:28 +00:00
Aleksei 957987bc6c feat: синхронизация светодиодов между пультами через /peer_led, v1.8 2026-06-17 11:29:30 +00:00
Aleksei 52740b26c6 fix: убрали retry, debounce 3500мс, версия 1.7 2026-06-17 11:03:32 +00:00
Aleksei cc206278c0 fix: retry 3 попытки + таймаут 2с для sendCommand 2026-06-17 11:01:59 +00:00
Aleksei cf5b241fe2 feat: debounce 2000мс, версия 1.5 2026-06-17 10:22:16 +00:00
Aleksei 5b8e0e8aaf feat: светодиод IO12 — горит во время debounce после нажатия кнопки 2026-06-17 10:20:10 +00:00
Aleksei beebb67fec fix: кнопка вне raw string, нет проблем с кавычками 2026-06-17 09:33:45 +00:00
Aleksei d543937c68 fix: HTML entities в onclick 2026-06-17 09:30:17 +00:00
Aleksei e704b00f5b fix: убрать unicode символ из строки 2026-06-17 09:27:04 +00:00
Aleksei 64b815f8c4 fix: откат к рабочей логике реле, кнопка через fetch+JS 2026-06-17 09:22:54 +00:00
Aleksei eaf6d16013 fix: triggerRelay до send(302) — как в контроллере 2026-06-17 09:16:55 +00:00
Aleksei 075e4d6c22 fix: relayPending флаг — реле срабатывает после отправки redirect 2026-06-17 09:10:25 +00:00
Aleksei 5166a155f6 fix: правильный POST redirect для barrier_node v1.3 2026-06-17 09:02:44 +00:00
Aleksei 2563d854bb fix: redirect 302, версии controller v1.3 node v1.2 2026-06-17 08:19:53 +00:00
Aleksei 3a219c5505 fix: кнопка открытия через POST + redirect, обновление страницы не срабатывает 2026-06-17 08:13:34 +00:00
Aleksei efa3d24b0d fix: redirect после команды — обновление страницы не повторяет команду 2026-06-17 08:11:22 +00:00
Aleksei bba7e4138a feat: версия прошивки в веб-морде (controller v1.2, node v1.1) 2026-06-17 08:09:59 +00:00
Aleksei 356c053905 fix: кнопки управления через POST — обновление страницы не срабатывает 2026-06-17 07:59:28 +00:00
Aleksei 51e711a9e4 feat: barrier_node v1.1 — рабочая версия, IO4, проверена на стенде 2026-06-15 13:12:44 +00:00
Aleksei 3c704320db fix: реле обратно на IO4 2026-06-15 12:12:46 +00:00
Aleksei 80722a68d9 fix: кнопка открытия через POST — перезагрузка страницы не срабатывает реле 2026-06-15 12:06:22 +00:00
2 changed files with 94 additions and 29 deletions
+73 -8
View File
@@ -11,6 +11,7 @@
#define BTN1_PIN 4
#define BTN2_PIN 2
#define LED_PIN 12
#define ETH_POWER_PIN 16
#define ETH_MDC_PIN 23
@@ -18,6 +19,7 @@
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
#define AP_SSID "Barrier-Setup"
#define FW_VERSION "1.9"
#define AP_PASS "barrier123"
Preferences prefs;
@@ -35,10 +37,11 @@ String cfg_token = "barrier_token_2026";
String cfg_self_ip = ""; // пусто = DHCP
String cfg_gateway = "";
String cfg_subnet = "255.255.255.0";
String cfg_peers[5] = {"", "", "", "", ""};
unsigned long lastPress1 = 0;
unsigned long lastPress2 = 0;
const unsigned long DEBOUNCE = 1500;
const unsigned long DEBOUNCE = 3500;
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
@@ -79,6 +82,9 @@ void loadConfig() {
cfg_self_ip = prefs.getString("self_ip", "");
cfg_gateway = prefs.getString("gateway", "");
cfg_subnet = prefs.getString("subnet", "255.255.255.0");
for (int i = 0; i < 5; i++) {
cfg_peers[i] = prefs.getString(("peer" + String(i)).c_str(), "");
}
prefs.end();
}
@@ -92,9 +98,23 @@ void saveConfig() {
prefs.putString("self_ip", cfg_self_ip);
prefs.putString("gateway", cfg_gateway);
prefs.putString("subnet", cfg_subnet);
for (int i = 0; i < 5; i++) {
prefs.putString(("peer" + String(i)).c_str(), cfg_peers[i]);
}
prefs.end();
}
void notifyPeer(int btn) {
for (int i = 0; i < 5; i++) {
if (cfg_peers[i].length() == 0) continue;
HTTPClient http;
http.begin("http://" + cfg_peers[i] + "/peer_led?b=" + String(btn));
http.addHeader("X-Token", cfg_token);
http.GET();
http.end();
}
}
void sendCommand(String ip) {
if (!ethConnected && !wifiConnected) {
Serial.println("Нет сети!");
@@ -141,7 +161,11 @@ input[type=text]:focus,input[type=password]:focus{outline:none;border-color:#219
<h2>Barrier Controller</h2>
<div class='sub'>IP: )";
html += currentIP();
html += "</div>";
html += " &nbsp;·&nbsp; v" + String(FW_VERSION) + "</div>";
String ok = server.hasArg("ok") ? server.arg("ok") : "";
if (ok == "1") msg = "✅ Команда → шлагбаум 1";
else if (ok == "2") msg = "✅ Команда → шлагбаум 2";
if (msg.startsWith(""))
html += "<div class='err'>" + msg + "</div>";
@@ -152,11 +176,11 @@ input[type=text]:focus,input[type=password]:focus{outline:none;border-color:#219
html += R"(<div class='card'>
<h3>Управление</h3>
<div class='row'>
<form action='/cmd' method='get' style='width:49%'>
<form action='/cmd' method='post' style='width:49%'>
<input type='hidden' name='b' value='1'>
<button class='btn btn-open' style='width:100%'> Шлагбаум 1</button>
</form>
<form action='/cmd' method='get' style='width:49%'>
<form action='/cmd' method='post' style='width:49%'>
<input type='hidden' name='b' value='2'>
<button class='btn btn-open' style='width:100%'> Шлагбаум 2</button>
</form>
@@ -181,6 +205,19 @@ input[type=text]:focus,input[type=password]:focus{outline:none;border-color:#219
<label>Пароль</label>
<input type='password' name='pass' placeholder='оставь пустым не менять'>
<hr class='sep'>
<h3>Другие пульты (синхронизация LED)</h3>
<label>Пульт 1 <span class='hint'>(пусто = не использовать)</span></label>
<input type='text' name='peer0' value=')" + cfg_peers[0] + R"(' placeholder='192.168.15.x'>
<label>Пульт 2 <span class='hint'>(пусто = не использовать)</span></label>
<input type='text' name='peer1' value=')" + cfg_peers[1] + R"(' placeholder='192.168.15.x'>
<label>Пульт 3 <span class='hint'>(пусто = не использовать)</span></label>
<input type='text' name='peer2' value=')" + cfg_peers[2] + R"(' placeholder='192.168.15.x'>
<label>Пульт 4 <span class='hint'>(пусто = не использовать)</span></label>
<input type='text' name='peer3' value=')" + cfg_peers[3] + R"(' placeholder='192.168.15.x'>
<label>Пульт 5 <span class='hint'>(пусто = не использовать)</span></label>
<input type='text' name='peer4' value=')" + cfg_peers[4] + R"(' placeholder='192.168.15.x'>
<hr class='sep'>
<h3>IP этого устройства</h3>
<label>Статический IP <span class='hint'>(пусто = DHCP)</span></label>
@@ -208,15 +245,27 @@ input[type=text]:focus,input[type=password]:focus{outline:none;border-color:#219
}
void setupRoutes() {
server.on("/peer_led", HTTP_GET, []() {
String token = server.header("X-Token");
if (token != cfg_token) { server.send(401, "text/plain", "unauthorized"); return; }
digitalWrite(LED_PIN, HIGH);
lastPress1 = millis(); // блокируем debounce
lastPress2 = millis();
server.send(200, "text/plain", "ok");
});
server.on("/", HTTP_GET, []() {
server.send(200, "text/html", buildPage());
});
server.on("/cmd", HTTP_GET, []() {
server.on("/cmd", HTTP_POST, []() {
String b = server.arg("b");
if (b == "1") { sendCommand(cfg_ip1); server.send(200, "text/html", buildPage("✅ Команда → шлагбаум 1")); }
else if (b == "2") { sendCommand(cfg_ip2); server.send(200, "text/html", buildPage("✅ Команда → шлагбаум 2")); }
else server.send(400, "text/plain", "bad request");
if (b == "1") { sendCommand(cfg_ip1); }
else if (b == "2") { sendCommand(cfg_ip2); }
else { server.send(400, "text/plain", "bad request"); return; }
// Redirect на главную — при обновлении страницы команда не повторится
server.sendHeader("Location", "/?ok=" + b);
server.send(302);
});
server.on("/save", HTTP_POST, []() {
@@ -227,6 +276,9 @@ void setupRoutes() {
cfg_self_ip = server.arg("self_ip");
cfg_gateway = server.arg("gateway");
cfg_subnet = server.arg("subnet");
for (int i = 0; i < 5; i++) {
cfg_peers[i] = server.arg(("peer" + String(i)).c_str());
}
String np = server.arg("pass");
if (np.length() > 0) cfg_pass = np;
saveConfig();
@@ -275,6 +327,8 @@ void setup() {
Serial.begin(115200);
pinMode(BTN1_PIN, INPUT_PULLUP);
pinMode(BTN2_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
loadConfig();
@@ -317,13 +371,24 @@ void loop() {
if (digitalRead(BTN1_PIN) == LOW && now - lastPress1 > DEBOUNCE) {
lastPress1 = now;
digitalWrite(LED_PIN, HIGH);
Serial.println("Кнопка 1");
sendCommand(cfg_ip1);
notifyPeer(1);
}
// Гасим светодиод когда debounce прошёл
if (now - lastPress1 < DEBOUNCE || now - lastPress2 < DEBOUNCE) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
if (digitalRead(BTN2_PIN) == LOW && now - lastPress2 > DEBOUNCE) {
lastPress2 = now;
digitalWrite(LED_PIN, HIGH);
Serial.println("Кнопка 2");
sendCommand(cfg_ip2);
notifyPeer(2);
}
delay(10);
+21 -21
View File
@@ -9,7 +9,7 @@
#include <WebServer.h>
#include <Update.h>
#define RELAY_PIN 15
#define RELAY_PIN 4
#define ETH_POWER_PIN 16
#define ETH_MDC_PIN 23
@@ -32,7 +32,7 @@ String cfg_token = "barrier_token_2026";
String cfg_self_ip = "";
String cfg_gateway = "";
String cfg_subnet = "255.255.255.0";
String cfg_name = "Шлагбаум 1"; // имя для идентификации
String cfg_name = "Шлагбаум 1";
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
@@ -89,9 +89,9 @@ void saveConfig() {
void triggerRelay() {
Serial.println("Реле: импульс 500мс");
digitalWrite(RELAY_PIN, LOW); // LOW = включить (NC размыкается)
digitalWrite(RELAY_PIN, HIGH);
delay(500);
digitalWrite(RELAY_PIN, HIGH); // HIGH = выключить
digitalWrite(RELAY_PIN, LOW);
}
String currentIP() {
@@ -129,20 +129,18 @@ input[type=text],input[type=password]{width:100%;box-sizing:border-box;padding:9
html += currentIP();
html += "</div>";
if (server.hasArg("ok")) msg = "✅ Команда выполнена";
if (msg.startsWith(""))
html += "<div class='err'>" + msg + "</div>";
else if (msg.length() > 0)
html += "<div class='msg'>" + msg + "</div>";
// Управление
html += R"(<div class='card'>
<h3>Управление</h3>
<form action='/open' method='get'>
<button class='btn btn-open'> Открыть шлагбаум</button>
</form>
</div>)";
html += "<div class='card'>";
html += "<h3>Управление</h3>";
html += "<button class='btn btn-open' onclick='fetch(\"/open\",{method:\"POST\"}).then(()=>window.location=\"/?ok=1\")'>Открыть</button>";
html += "</div>";
// Настройки
html += R"(<div class='card'>
<form action='/save' method='post'>
<h3>Устройство</h3>
@@ -150,14 +148,12 @@ input[type=text],input[type=password]{width:100%;box-sizing:border-box;padding:9
<input type='text' name='name' value=')" + cfg_name + R"('>
<label>API токен</label>
<input type='text' name='token' value=')" + cfg_token + R"('>
<hr class='sep'>
<h3>Сеть WiFi</h3>
<label>SSID</label>
<input type='text' name='ssid' value=')" + cfg_ssid + R"('>
<label>Пароль</label>
<input type='password' name='pass' placeholder='оставь пустым не менять'>
<hr class='sep'>
<h3>IP этого устройства</h3>
<label>Статический IP <span class='hint'>(пусто = DHCP)</span></label>
@@ -166,12 +162,10 @@ input[type=text],input[type=password]{width:100%;box-sizing:border-box;padding:9
<input type='text' name='gateway' value=')" + cfg_gateway + R"(' placeholder='например 192.168.15.1'>
<label>Маска подсети</label>
<input type='text' name='subnet' value=')" + cfg_subnet + R"('>
<button class='btn btn-primary' type='submit'>💾 Сохранить и перезагрузить</button>
</form>
</div>)";
// OTA
html += R"(<div class='card'>
<h3>Обновление прошивки</h3>
<form action='/update' method='post' enctype='multipart/form-data'>
@@ -189,11 +183,18 @@ void setupRoutes() {
server.send(200, "text/html", buildPage());
});
// Открытие через браузер (без токена)
server.on("/open", HTTP_POST, []() {
String token = server.header("X-Token");
if (token.length() > 0 && token != cfg_token) {
server.send(401, "text/plain", "unauthorized");
return;
}
triggerRelay();
server.send(200, "text/plain", "ok");
});
server.on("/open", HTTP_GET, []() {
String token = server.header("X-Token");
// Если запрос из браузера (нет токена) — разрешаем
// Если запрос от контроллера — проверяем токен
if (token.length() > 0 && token != cfg_token) {
server.send(401, "text/plain", "unauthorized");
return;
@@ -244,7 +245,6 @@ void setupRoutes() {
}
);
// Статус
server.on("/status", HTTP_GET, []() {
String json = "{\"name\":\"" + cfg_name + "\",\"ip\":\"" + currentIP() + "\",\"eth\":" + (ethConnected ? "true" : "false") + "}";
server.send(200, "application/json", json);
@@ -254,7 +254,7 @@ void setupRoutes() {
void setup() {
Serial.begin(115200);
digitalWrite(RELAY_PIN, HIGH); // HIGH = реле выключено (инвертированная логика)
digitalWrite(RELAY_PIN, LOW);
pinMode(RELAY_PIN, OUTPUT);
loadConfig();