diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5dac9f5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.pioenvs
+.piolibdeps
+.clang_complete
+.gcc-flags.json
diff --git a/data/index.html b/data/index.html
new file mode 100644
index 0000000..793177d
--- /dev/null
+++ b/data/index.html
@@ -0,0 +1,24 @@
+
+
+
+ Domofon
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/script.js b/data/script.js
new file mode 100644
index 0000000..d39cc94
--- /dev/null
+++ b/data/script.js
@@ -0,0 +1,63 @@
+(function() {
+ var terminal = document.getElementById('terminal'),
+ clear = document.getElementById('clear'),
+ restart = document.getElementById('restart'),
+ ws = null,
+ reconnectTimeout = null,
+ prefix = '';
+
+ function _connect() {
+ if (!ws || ws.readyState === WebSocket.CLOSED) {
+ try {
+ ws = new WebSocket('ws://' + window.location.hostname + '/ws');
+ ws.onopen = _onOpen;
+ ws.onmessage = _onMessage;
+ ws.onclose = _onClose;
+ } catch (e) {
+ _onClose();
+ }
+ }
+ }
+
+ function _onOpen() {
+ clearTimeout(reconnectTimeout);
+ }
+
+ function _onClose(e) {
+ var code = e && e.code || 1012;
+ ws = null;
+ if (code > 1000) {
+ reconnectTimeout = setTimeout(_connect, 1000);
+ }
+ }
+
+ function _onMessage(message) {
+ var data = message && message.data;
+ if (data) {
+ data = prefix + data;
+
+ if (data.endsWith("\n")) {
+ prefix = "\n";
+ data = data.substr(0, data.length - 1);
+ } else {
+ prefix = '';
+ }
+
+ terminal.value += data;
+ terminal.scrollTop = terminal.scrollHeight;
+ }
+ }
+
+ clear.addEventListener('click', function(e) {
+ terminal.value = '';
+ prefix = '';
+ });
+
+ restart.addEventListener('click', function(e) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', '/restart', true);
+ xhr.send(null);
+ });
+
+ _connect();
+})();
diff --git a/data/style.css b/data/style.css
new file mode 100644
index 0000000..caa4d43
--- /dev/null
+++ b/data/style.css
@@ -0,0 +1,80 @@
+html, body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ font-family: sans-serif;
+}
+
+.content {
+ line-height: 1.6em;
+ margin: 0 auto;
+ padding: 30px 0 50px;
+ max-width: 50pc;
+ padding: 0 2em;
+ overflow: hidden;
+}
+
+h2 {
+ font-size: 3em;
+ font-weight: 300;
+ margin: .5em 0 .3em;
+ text-align: center;
+ line-height: 1em;
+}
+
+input, textarea {
+ border-radius: 4px;
+ padding: .5em .6em;
+ box-sizing: border-box;
+ outline: none;
+}
+
+input {
+ height: 36px;
+ width: 20%;
+ min-width: 140px;
+ font-family: sans-serif;
+ vertical-align: middle;
+ line-height: normal;
+ display: inline-block;
+ white-space: nowrap;
+ font-size: 100%;
+ cursor: pointer;
+ user-select: none;
+ color: #fff;
+ text-shadow: 0 1px 1px rgba(0,0,0,.2);
+ text-align: center;
+}
+
+input:active {
+ box-shadow: 0 0 0 1px rgba(0,0,0,.15) inset, 0 0 6px rgba(0,0,0,.2) inset;
+}
+
+.controls {
+ margin: .5em 0 1em;
+}
+
+#clear {
+ background: #009a3e;
+}
+
+#restart {
+ float: right;
+ background: #c01200;
+}
+
+#terminal {
+ display: block;
+ width: 100%;
+ background: #222;
+ height: 25pc;
+ max-height: 60vh;
+ color: #c9ea7b;
+ font-family: Courier New, monospace;
+ font-size: 80%;
+ line-height: 110%;
+ resize: none;
+ box-shadow: inset 0 1px 3px #ddd;
+ border: 1px solid #ccc;
+}
diff --git a/lib/readme.txt b/lib/readme.txt
new file mode 100644
index 0000000..cfa16df
--- /dev/null
+++ b/lib/readme.txt
@@ -0,0 +1,41 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link them to executable files.
+
+The source code of each library should be placed in separate directories, like
+"lib/private_lib/[here are source files]".
+
+For example, see the structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+| |
+| |--Bar
+| | |--docs
+| | |--examples
+| | |--src
+| | |- Bar.c
+| | |- Bar.h
+| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+| |
+| |--Foo
+| | |- Foo.c
+| | |- Foo.h
+| |
+| |- readme.txt --> THIS FILE
+|
+|- platformio.ini
+|--src
+ |- main.c
+
+Then in `src/main.c` you should use:
+
+#include
+#include
+
+// rest H/C/CPP code
+
+PlatformIO will find your libraries automatically, configure preprocessor's
+include paths and build them.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 0000000..7bbfda2
--- /dev/null
+++ b/platformio.ini
@@ -0,0 +1,33 @@
+; PlatformIO Project Configuration File
+;
+; Build options: build flags, source filter
+; Upload options: custom upload port, speed and extra flags
+; Library options: dependencies, extra library storages
+; Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[platformio]
+env_default = nodemcuv2
+src_dir = src
+
+[env:nodemcuv2]
+platform = espressif8266
+board = nodemcuv2
+framework = arduino
+
+lib_deps =
+ PubSubClient
+ Bounce2
+ ESP Async WebServer
+
+; targets = upload, uploadfs
+
+upload_port =
+upload_flags = --auth=domofon
+
+; upload_port = /dev/cu.wchusbserial1430
+; upload_speed = 115200
+; monitor_port = /dev/cu.wchusbserial1430
+; monitor_speed = 115200
diff --git a/src/config/hardware.h b/src/config/hardware.h
new file mode 100644
index 0000000..fa9006a
--- /dev/null
+++ b/src/config/hardware.h
@@ -0,0 +1,17 @@
+#ifndef HW_H_
+#define HW_H_
+
+// Hardware configuration
+#define PIN_RELAY_ANSWER 16 //D0
+#define PIN_RELAY_DOOR_OPEN 5 //D1
+
+#define PIN_LED_RED 4 //D2
+#define PIN_LED_GREEN 0 //D3
+#define PIN_LED_BLUE 2 //D4
+
+#define PIN_CALL_DETECT 14 //D5
+
+#define PIN_BUTTON_GREEN 12 //D6
+#define PIN_BUTTON_RED 13 //D7
+
+#endif // HW_H_
diff --git a/src/config/mqtt.h b/src/config/mqtt.h
new file mode 100644
index 0000000..3cadd6c
--- /dev/null
+++ b/src/config/mqtt.h
@@ -0,0 +1,19 @@
+#ifndef MQTT_H_
+#define MQTT_H_
+
+// High level protocol messages
+#define MSG_STATUS_READY "R" // ready; sent after successfull boot-up or after receiving of 'P' message
+#define MSG_STATUS_LAST_WILL "L" // last will message; send when device goes offline
+
+#define MSG_OUT_CALL "C" // call; sent after detecting of incoming intercom call
+#define MSG_OUT_HANGUP "H" // hangup; sent after detected incoming call finished
+#define MSG_OUT_OPENED_BY_BUTTON "B" // button; sent when "door open" has been performed by green hw button press
+#define MSG_OUT_REJECTED_BY_BUTTON "J" // reJected; sent when incoming call has been rejected by red hw button press
+#define MSG_OUT_SUCCESS "S" // success; sent in response to 'O' or 'N' command
+#define MSG_OUT_FAIL "F" // fail; sent in response to 'O' or 'N' command (this means that 'O' or 'N' command has been received but no incoming call detected)
+
+#define MSG_IN_OPEN 'O' // door open command
+#define MSG_IN_REJECT 'N' // call reject command (door will not open)
+#define MSG_IN_PING 'P' // ping command (answers with 'R')
+
+#endif // MQTT_H_
diff --git a/src/config/software.h b/src/config/software.h
new file mode 100644
index 0000000..43081a1
--- /dev/null
+++ b/src/config/software.h
@@ -0,0 +1,24 @@
+#ifndef SW_H_
+#define SW_H_
+
+// Software configuration
+#define HOST_NAME "domofon"
+#define HOST_PASSWORD "domofon"
+#define OTA_PORT 8266
+#define WIFI_SSID ""
+#define WIFI_PASSWORD ""
+
+#define MQTT_SERVER_ADDR ""
+#define MQTT_SERVER_PORT 1883
+#define MQTT_USER_NAME ""
+#define MQTT_USER_PASSWORD ""
+#define MQTT_CLIENT_ID "domofon"
+#define MQTT_TOPIC_IN "domofon/in"
+#define MQTT_TOPIC_OUT "domofon/out"
+#define MQTT_TOPIC_STATUS "domofon/status"
+
+#define CALL_HANGUP_DETECT_DELAY 3000
+#define RELAY_ANSWER_ON_TIME 1500
+#define RELAY_OPEN_ON_TIME 600
+
+#endif // SW_H_
diff --git a/src/debug.ino b/src/debug.ino
new file mode 100644
index 0000000..0d3668b
--- /dev/null
+++ b/src/debug.ino
@@ -0,0 +1,12 @@
+#include "inc/include.h"
+
+void DEBUG_LN(const char *text) {
+ Serial.println(text);
+ webSocket().printfAll("%s\n", text);
+}
+
+template
+void DEBUG_F(const char *format, Args... args) {
+ Serial.printf(format, args...);
+ webSocket().printfAll(format, args...);
+}
diff --git a/src/domofon.ino b/src/domofon.ino
new file mode 100644
index 0000000..b83e7bb
--- /dev/null
+++ b/src/domofon.ino
@@ -0,0 +1,145 @@
+#include "inc/include.h"
+
+EState state = IDLE;
+EAction action = NO_ACTION;
+
+Bounce debouncerBtnGreen = Bounce();
+Bounce debouncerBtnRed = Bounce();
+
+unsigned long lastCallDetectedTime = 0;
+
+void callAnswer() {
+ DEBUG_LN("[HW] Call answer...");
+ digitalWrite(PIN_RELAY_DOOR_OPEN, RELAY_OFF);
+ digitalWrite(PIN_RELAY_ANSWER, RELAY_ON);
+ DEBUG_LN("[HW] Done");
+}
+
+void callHangUp() {
+ DEBUG_LN("[HW] Hang up...");
+ digitalWrite(PIN_RELAY_ANSWER, RELAY_OFF);
+ digitalWrite(PIN_RELAY_DOOR_OPEN, RELAY_OFF);
+ DEBUG_LN("[HW] Done");
+}
+
+void doorOpen() {
+ DEBUG_LN("[HW] Door open...");
+ digitalWrite(PIN_RELAY_DOOR_OPEN, RELAY_ON);
+ delay(RELAY_OPEN_ON_TIME);
+ digitalWrite(PIN_RELAY_DOOR_OPEN, RELAY_OFF);
+ DEBUG_LN("[HW] Done");
+}
+
+void answerAndOpen() {
+ callAnswer();
+ delay(RELAY_ANSWER_ON_TIME);
+ doorOpen();
+ callHangUp();
+}
+
+void answerAndReject() {
+ callAnswer();
+ delay(RELAY_ANSWER_ON_TIME);
+ callHangUp();
+}
+
+void handleIdle(EState oldState) {
+ if (oldState != IDLE) {
+ mqttSendCommand(MSG_OUT_HANGUP);
+ ledOff();
+ DEBUG_LN("[HW] Current state: IDLE");
+ }
+
+ if (action != NO_ACTION) {
+ mqttSendCommand(MSG_OUT_FAIL);
+ action = NO_ACTION;
+ }
+
+ if (debouncerBtnGreen.fell()) {
+ DEBUG_LN("[HW] Button click");
+ ledBlink(PIN_LED_GREEN, 2);
+ }
+}
+
+void handleCall(EState oldState) {
+ if (oldState != CALL) {
+ action = NO_ACTION;
+ mqttSendCommand(MSG_OUT_CALL);
+ ledOn(PIN_LED_RED);
+ DEBUG_LN("[HW] Current state: CALL");
+ }
+
+ if (action == NO_ACTION) {
+ if (debouncerBtnRed.fell()) {
+ action = REJECT_BY_BUTTON;
+ } else if (debouncerBtnGreen.fell()) {
+ action = OPEN_BY_BUTTON;
+ }
+ }
+
+ switch (action) {
+ case OPEN_BY_BUTTON:
+ answerAndOpen();
+ mqttSendCommand(MSG_OUT_OPENED_BY_BUTTON);
+ break;
+
+ case REJECT_BY_BUTTON:
+ answerAndReject();
+ mqttSendCommand(MSG_OUT_REJECTED_BY_BUTTON);
+ break;
+
+ case OPEN:
+ answerAndOpen();
+ mqttSendCommand(MSG_OUT_SUCCESS);
+ break;
+
+ case REJECT:
+ answerAndReject();
+ mqttSendCommand(MSG_OUT_SUCCESS);
+ break;
+
+ default:
+ break;
+ }
+
+ action = NO_ACTION;
+}
+
+void setStateIdle() {
+ state = IDLE;
+}
+
+void setStateCall() {
+ state = CALL;
+}
+
+void domofonSetup() {
+ debouncerBtnGreen.attach(PIN_BUTTON_GREEN);
+ debouncerBtnGreen.interval(25);
+ debouncerBtnRed.attach(PIN_BUTTON_RED);
+ debouncerBtnRed.interval(25);
+}
+
+void domofonLoop() {
+ debouncerBtnGreen.update();
+ debouncerBtnRed.update();
+
+ EState oldState = state;
+
+ if (digitalRead(PIN_CALL_DETECT) == LOW) {
+ setStateCall();
+ lastCallDetectedTime = millis();
+ } else if (millis() - lastCallDetectedTime > CALL_HANGUP_DETECT_DELAY) {
+ setStateIdle();
+ }
+
+ switch (state) {
+ case IDLE:
+ handleIdle(oldState);
+ break;
+
+ case CALL:
+ handleCall(oldState);
+ break;
+ }
+}
diff --git a/src/hardware.ino b/src/hardware.ino
new file mode 100644
index 0000000..80d0509
--- /dev/null
+++ b/src/hardware.ino
@@ -0,0 +1,28 @@
+#include "inc/include.h"
+
+Ticker _defer_restart;
+
+void hardwareSetup() {
+ pinMode(PIN_BUTTON_GREEN, INPUT_PULLUP);
+ pinMode(PIN_BUTTON_RED, INPUT_PULLUP);
+ pinMode(PIN_CALL_DETECT, INPUT_PULLUP);
+ pinMode(PIN_LED_RED, OUTPUT);
+ pinMode(PIN_LED_GREEN, OUTPUT);
+ pinMode(PIN_LED_BLUE, OUTPUT);
+ pinMode(PIN_RELAY_ANSWER, OUTPUT);
+ pinMode(PIN_RELAY_DOOR_OPEN, OUTPUT);
+
+ digitalWrite(PIN_LED_RED, LED_OFF);
+ digitalWrite(PIN_LED_GREEN, LED_OFF);
+ digitalWrite(PIN_LED_BLUE, LED_OFF);
+ digitalWrite(PIN_RELAY_ANSWER, RELAY_OFF);
+ digitalWrite(PIN_RELAY_DOOR_OPEN, RELAY_OFF);
+}
+
+void restart() {
+ ESP.reset();
+}
+
+void deferredRestart(unsigned long delay) {
+ _defer_restart.once_ms(delay, restart);
+}
diff --git a/src/inc/include.h b/src/inc/include.h
new file mode 100644
index 0000000..667de89
--- /dev/null
+++ b/src/inc/include.h
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../config/hardware.h"
+#include "../config/software.h"
+#include "../config/mqtt.h"
+#include "types.h"
diff --git a/src/inc/types.h b/src/inc/types.h
new file mode 100644
index 0000000..932b0e7
--- /dev/null
+++ b/src/inc/types.h
@@ -0,0 +1,22 @@
+#ifndef TYPES_H_
+#define TYPES_H_
+
+#define LED_ON HIGH
+#define LED_OFF LOW
+#define RELAY_ON LOW
+#define RELAY_OFF HIGH
+
+typedef enum {
+ IDLE,
+ CALL
+} EState;
+
+typedef enum {
+ NO_ACTION,
+ OPEN,
+ OPEN_BY_BUTTON,
+ REJECT,
+ REJECT_BY_BUTTON
+} EAction;
+
+#endif // TYPES_H_
diff --git a/src/led.ino b/src/led.ino
new file mode 100644
index 0000000..a34bff4
--- /dev/null
+++ b/src/led.ino
@@ -0,0 +1,21 @@
+#include "inc/include.h"
+
+void ledBlink(int pin, int count) {
+ for (int i = 0; i < count; i++) {
+ digitalWrite(pin, LED_ON);
+ delay(125);
+ digitalWrite(pin, LED_OFF);
+ delay(125);
+ }
+}
+
+void ledOff() {
+ digitalWrite(PIN_LED_GREEN, LED_OFF);
+ digitalWrite(PIN_LED_RED, LED_OFF);
+ digitalWrite(PIN_LED_BLUE, LED_OFF);
+}
+
+void ledOn(int pin) {
+ ledOff();
+ digitalWrite(pin, LED_ON);
+}
diff --git a/src/main.ino b/src/main.ino
new file mode 100644
index 0000000..515b3cb
--- /dev/null
+++ b/src/main.ino
@@ -0,0 +1,20 @@
+#include "inc/include.h"
+
+// Код частично заимствован и переделан
+// @see https://github.com/Metori/mqtt_domofon
+
+void setup() {
+ hardwareSetup();
+ wifiSetup();
+ otaSetup();
+ mqttSetup();
+ webServerSetup();
+ domofonSetup();
+}
+
+void loop() {
+ wifiLoop();
+ otaLoop();
+ mqttLoop();
+ domofonLoop();
+}
diff --git a/src/mqtt.ino b/src/mqtt.ino
new file mode 100644
index 0000000..894516f
--- /dev/null
+++ b/src/mqtt.ino
@@ -0,0 +1,89 @@
+#include "inc/include.h"
+
+WiFiClient espClient;
+PubSubClient mqttClient(espClient);
+
+void mqttSendCommand(const char *msg) {
+ mqttClient.publish(MQTT_TOPIC_OUT, msg, 1);
+ DEBUG_F("[MQTT] Message sent: %s\n", msg);
+}
+
+void mqttSendStatus(const char *msg) {
+ mqttClient.publish(MQTT_TOPIC_STATUS, msg, 1);
+ DEBUG_F("[MQTT] Status sent: %s\n", msg);
+}
+
+void onMqttMsgReceived(char* topic, byte* payload, unsigned int len) {
+ if (len != 1) {
+ char* command = (char*)malloc(len + 2);
+ memcpy(command, payload, len);
+ command[len] = '\0';
+
+ DEBUG_F("[MQTT] Message received [%u]: %s\n", len, command);
+ return;
+ }
+
+ char cmd = (char)payload[0];
+ DEBUG_F("[MQTT] Command received: %c\n", cmd);
+
+ switch (cmd) {
+ case MSG_IN_OPEN:
+ action = OPEN;
+ break;
+
+ case MSG_IN_REJECT:
+ action = REJECT;
+ break;
+
+ case MSG_IN_PING:
+ mqttSendStatus(MSG_STATUS_READY);
+ break;
+
+ default:
+ DEBUG_LN("[MQTT] Unknown command");
+ break;
+ }
+}
+
+void mqttConnect() {
+ DEBUG_F("[MQTT] (Re)connecting to server on %s...\n", MQTT_SERVER_ADDR);
+
+ for (int i = 0; !mqttClient.connected(); i++) {
+ // Если не получилось за 5 попыток - перезагружаемся
+ if (i >= 5) {
+ ESP.restart();
+ return;
+ }
+
+ if (!mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER_NAME, MQTT_USER_PASSWORD, MQTT_TOPIC_STATUS, 0, 0, MSG_STATUS_LAST_WILL)) {
+ // Ждем 2 секунды
+ DEBUG_F(".");
+ ledBlink(PIN_LED_GREEN, 8);
+ }
+ }
+
+ DEBUG_LN("\n[MQTT] Done");
+ mqttSendStatus(MSG_STATUS_READY);
+ mqttClient.subscribe(MQTT_TOPIC_IN);
+
+ setStateIdle();
+ DEBUG_LN("[MQTT] Current state: IDLE");
+}
+
+void mqttStop() {
+ mqttClient.disconnect();
+ ledOff();
+}
+
+void mqttSetup() {
+ mqttClient.setServer(MQTT_SERVER_ADDR, MQTT_SERVER_PORT);
+ mqttClient.setCallback(onMqttMsgReceived);
+ mqttConnect();
+}
+
+void mqttLoop() {
+ if (!mqttClient.connected()) {
+ mqttConnect();
+ }
+ mqttClient.loop();
+}
diff --git a/src/ota.ino b/src/ota.ino
new file mode 100644
index 0000000..f709297
--- /dev/null
+++ b/src/ota.ino
@@ -0,0 +1,58 @@
+#include "inc/include.h"
+
+void otaSetup() {
+ ArduinoOTA.setHostname(HOST_NAME);
+ ArduinoOTA.setPort(OTA_PORT);
+
+ ArduinoOTA.setPassword(HOST_PASSWORD);
+
+ ArduinoOTA.onStart([]() {
+ DEBUG_LN("[OTA] Start update");
+ webServerStop();
+ mqttStop();
+ });
+
+ ArduinoOTA.onEnd([]() {
+ DEBUG_LN("[OTA] End update");
+ });
+
+ ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
+ DEBUG_F("[OTA] Progress: %u%%\n", (progress / (total / 100)));
+ });
+
+ ArduinoOTA.onError([](ota_error_t error) {
+ String type;
+ switch (error) {
+ // "Ошибка при аутентификации"
+ case OTA_AUTH_ERROR:
+ type = "Auth Failed";
+ break;
+ // "Ошибка при начале OTA-апдейта"
+ case OTA_BEGIN_ERROR:
+ type = "Begin Failed";
+ break;
+ // "Ошибка при подключении"
+ case OTA_CONNECT_ERROR:
+ type = "Connect Failed";
+ break;
+ // "Ошибка при получении данных"
+ case OTA_RECEIVE_ERROR:
+ type = "Receive Failed";
+ break;
+ // "Ошибка при завершении OTA-апдейта"
+ case OTA_END_ERROR:
+ type = "End Failed";
+ break;
+ default:
+ type = "Unknown";
+ }
+
+ DEBUG_F("[OTA] Error[%u]: %s\n", error, type.c_str());
+ });
+
+ ArduinoOTA.begin();
+}
+
+void otaLoop() {
+ ArduinoOTA.handle();
+}
diff --git a/src/server.ino b/src/server.ino
new file mode 100644
index 0000000..b26a66f
--- /dev/null
+++ b/src/server.ino
@@ -0,0 +1,72 @@
+#include "inc/include.h"
+
+AsyncWebServer _server(80);
+AsyncWebSocket _ws("/ws");
+
+AsyncWebSocket webSocket() {
+ return _ws;
+}
+
+void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
+ uint32_t id = client->id();
+
+ switch (type) {
+ case WS_EVT_CONNECT:
+ DEBUG_F("[WS] Client connected - ID: %u\n", id);
+ client->ping();
+ break;
+
+ case WS_EVT_DISCONNECT:
+ DEBUG_F("[WS] Client disconnected - ID: %u\n", id);
+ break;
+
+ case WS_EVT_ERROR:
+ DEBUG_F("[WS] Error - ID: %u, code: %u, error: %s\n", id, *((uint16_t*)arg), (char*)data);
+ break;
+
+ case WS_EVT_PONG:
+ DEBUG_F("[WS] Pong - ID: %u\n", id);
+ break;
+
+ case WS_EVT_DATA:
+ AwsFrameInfo * info = (AwsFrameInfo*)arg;
+ if (info->final && info->index == 0 && info->len == len) {
+ if (info->opcode == WS_TEXT) {
+ DEBUG_F("[WS] Message received - ID: %u, message: %s\n", id, (char*)data);
+ } else {
+ DEBUG_F("[WS] Bynary messages not supported - ID: %u\n", id);
+ }
+ } else if (info->final && (info->index + len) == info->len) {
+ DEBUG_F("[WS] Message too long - ID: %u\n", id);
+ }
+ break;
+ }
+}
+
+void webServerStop() {
+ DEBUG_LN("[WS] Server stopped");
+ SPIFFS.end();
+ _ws.enable(false);
+ _ws.closeAll(1012);
+}
+
+void webServerSetup() {
+ SPIFFS.begin();
+
+ _server.rewrite("/", "/index.html");
+ _server.onNotFound([](AsyncWebServerRequest *request) {
+ request->send(404, "text/plain", "404: Not Found");
+ });
+ _server.on("/restart", HTTP_POST, [](AsyncWebServerRequest *request) {
+ request->send(200);
+ DEBUG_LN("Restarting...");
+ webServerStop();
+ mqttStop();
+ deferredRestart(200);
+ });
+ _server.serveStatic("/", SPIFFS, "/");
+ _server.begin();
+
+ _ws.onEvent(_wsEvent);
+ _server.addHandler(&_ws);
+}
diff --git a/src/wifi.ino b/src/wifi.ino
new file mode 100644
index 0000000..a36c1c0
--- /dev/null
+++ b/src/wifi.ino
@@ -0,0 +1,44 @@
+#include "inc/include.h"
+
+void wifiDisconnect() {
+ WiFi.disconnect();
+ ledOff();
+}
+
+void wifiConnect() {
+ DEBUG_F("[WIFI] (Re)connecting to \"%s\"\n", WIFI_SSID);
+
+ WiFi.mode(WIFI_STA);
+ WiFi.hostname(HOST_NAME);
+ WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
+
+ for (int i = 0; WiFi.status() != WL_CONNECTED; i++) {
+ // Если не получилось за 5 попыток - перезагружаемся
+ if (i >= 5) {
+ ESP.restart();
+ return;
+ }
+
+ // Ждем 2 секунды
+ DEBUG_F(".");
+ ledBlink(PIN_LED_BLUE, 8);
+ }
+
+ DEBUG_LN("\n[WIFI] Done");
+ DEBUG_F("[WIFI] IP address: %s\n", WiFi.localIP().toString().c_str());
+}
+
+void wifiReconnect() {
+ wifiDisconnect();
+ wifiConnect();
+}
+
+void wifiSetup() {
+ wifiConnect();
+}
+
+void wifiLoop() {
+ if (WiFi.status() != WL_CONNECTED) {
+ wifiReconnect();
+ }
+}