mirror of
https://github.com/Anonym-tsk/smart-domofon.git
synced 2025-12-07 20:17:00 +03:00
It works
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.pioenvs
|
||||||
|
.piolibdeps
|
||||||
|
.clang_complete
|
||||||
|
.gcc-flags.json
|
||||||
24
data/index.html
Normal file
24
data/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Domofon</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4QkUEBQ2PpUhggAAAM5JREFUOMut0rFKQzEUANBDXwfpWnDoB9i5v1C/wJ9wcLCCu6sfIAodWwehLh3qIrjW1T8QB10FEV2kdcmDy4O25tlAICHJyc3N5Z+tCOMO5mjhsQ52iWXqp7mHh9jDLCBHOcAnngOywGEO8JZuLZGD3CfchtAf6iRwPwAvaNZBbgJyjUYusBtyscRVBWngAsfrkB4+AjLGTlob/LVO+hXkHU/oVurkZFMkr2Fz+cVd3KX5ZFNO2hitQM7KpxVrgG9McZ8qs0jIF87xYxvtFxFQQMNN792iAAAAAElFTkSuQmCC">
|
||||||
|
<link rel="stylesheet" href="style.css"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Domofon</h2>
|
||||||
|
|
||||||
|
<textarea id="terminal" readonly></textarea>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<input id="clear" type="button" value="Clear"/>
|
||||||
|
<input id="restart" type="button" value="Restart"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
63
data/script.js
Normal file
63
data/script.js
Normal file
@@ -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();
|
||||||
|
})();
|
||||||
80
data/style.css
Normal file
80
data/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
41
lib/readme.txt
Normal file
41
lib/readme.txt
Normal file
@@ -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 <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
33
platformio.ini
Normal file
33
platformio.ini
Normal file
@@ -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
|
||||||
17
src/config/hardware.h
Normal file
17
src/config/hardware.h
Normal file
@@ -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_
|
||||||
19
src/config/mqtt.h
Normal file
19
src/config/mqtt.h
Normal file
@@ -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_
|
||||||
24
src/config/software.h
Normal file
24
src/config/software.h
Normal file
@@ -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_
|
||||||
12
src/debug.ino
Normal file
12
src/debug.ino
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include "inc/include.h"
|
||||||
|
|
||||||
|
void DEBUG_LN(const char *text) {
|
||||||
|
Serial.println(text);
|
||||||
|
webSocket().printfAll("%s\n", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void DEBUG_F(const char *format, Args... args) {
|
||||||
|
Serial.printf(format, args...);
|
||||||
|
webSocket().printfAll(format, args...);
|
||||||
|
}
|
||||||
145
src/domofon.ino
Normal file
145
src/domofon.ino
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/hardware.ino
Normal file
28
src/hardware.ino
Normal file
@@ -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);
|
||||||
|
}
|
||||||
14
src/inc/include.h
Normal file
14
src/inc/include.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <RemoteDebug.h>
|
||||||
|
#include <PubSubClient.h>
|
||||||
|
#include <Bounce2.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <Ticker.h>
|
||||||
|
|
||||||
|
#include "../config/hardware.h"
|
||||||
|
#include "../config/software.h"
|
||||||
|
#include "../config/mqtt.h"
|
||||||
|
#include "types.h"
|
||||||
22
src/inc/types.h
Normal file
22
src/inc/types.h
Normal file
@@ -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_
|
||||||
21
src/led.ino
Normal file
21
src/led.ino
Normal file
@@ -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);
|
||||||
|
}
|
||||||
20
src/main.ino
Normal file
20
src/main.ino
Normal file
@@ -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();
|
||||||
|
}
|
||||||
89
src/mqtt.ino
Normal file
89
src/mqtt.ino
Normal file
@@ -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();
|
||||||
|
}
|
||||||
58
src/ota.ino
Normal file
58
src/ota.ino
Normal file
@@ -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();
|
||||||
|
}
|
||||||
72
src/server.ino
Normal file
72
src/server.ino
Normal file
@@ -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);
|
||||||
|
}
|
||||||
44
src/wifi.ino
Normal file
44
src/wifi.ino
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user