Compare commits
24 Commits
b4bbd455ae
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a40d324ab6 | |||
| 056f4030e1 | |||
| 957987bc6c | |||
| 52740b26c6 | |||
| cc206278c0 | |||
| cf5b241fe2 | |||
| 5b8e0e8aaf | |||
| beebb67fec | |||
| d543937c68 | |||
| e704b00f5b | |||
| 64b815f8c4 | |||
| eaf6d16013 | |||
| 075e4d6c22 | |||
| 5166a155f6 | |||
| 2563d854bb | |||
| 3a219c5505 | |||
| efa3d24b0d | |||
| bba7e4138a | |||
| 356c053905 | |||
| 51e711a9e4 | |||
| 3c704320db | |||
| 80722a68d9 | |||
| 090ff7aa9a | |||
| ae2cd6e3d6 |
@@ -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 += " · 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);
|
||||
|
||||
@@ -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 = включить
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user