Code moved

This commit is contained in:
Nikolay Vasilchuk
2021-02-09 15:22:27 +03:00
parent 5e1ed29f46
commit de4776eaa8
25 changed files with 14 additions and 14 deletions

69
native/README.md Normal file
View File

@@ -0,0 +1,69 @@
# Умный домофон на ESP8266
[English](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/README_EN.md) | **Русский**
Устройство умеет открывать домофон при входящем вызове. Работает только с координатными домофонами. Проверено с домофонами Visit, Cyfral, Metakom, Altis.
---
Нравится проект? [Поддержи автора](http://yasobe.ru/na/esphome)! Купи ему немного :beers: или :coffee:!
[![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](http://yasobe.ru/na/esphome)
---
## Используемые компоненты
* NodeMCU v3 x 1
* Двойной релейный модуль x1
* RGB светодиод x1
* Тактовая кнопка x1
* Оптрон x1
* Несколько резисторов
Вместо NodeMCU можно всять почти любую ESP с достаточным количеством GPIO (например, Wemos D1 Mini). Кнопка и светодиод не обязательны.
## Пример компонентов
1. [Оптопара PC817B](https://roboshop.spb.ru/PC817B)
2. [Реле электромеханическое 2-канальное](https://roboshop.spb.ru/SRD-05VDC-SL-C-2-channel-rele)
3. [NodeMCU V3](https://roboshop.spb.ru/NodeMCU-v3-dev-board)
4. [RGB светодиод на плате](https://roboshop.spb.ru/RGB-led-module)
5. [Кнопка тактовая 6х6х13мм](https://roboshop.spb.ru/KFC-A06-13H)
6. [Провода "мама-мама" 10см](https://roboshop.spb.ru/female-to-female-line)
7. [Провода "папа-мама" 10см](https://roboshop.spb.ru/male-to-female-line)
8. [Резисторы](https://roboshop.spb.ru/600-resist-set)
9. [microUSB кабель](https://roboshop.spb.ru/BS-410)
## Схема
![Scheme](https://raw.githubusercontent.com/Anonym-tsk/smart-domofon/master/native/scheme.jpeg)
*За схему спасибо Oleg Yu*
### Эта версия прошивки больше не поддерживается. Пожалуйста, используйте [новую прошивку на базе ESPHome](https://github.com/Anonym-tsk/smart-domofon/blob/master/esphome/README.md).
## Конфигурация и прошивка
1. Заполните настройки WiFi и MQTT в файле [software.h](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/src/config/software.h)
2. Укажите `upload_port` в файле [platformio.ini](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/platformio.ini)
3. Используйте [PlatformIO](https://platformio.org/platformio-ide) для компиляции и загрузки прошивки
## MQTT сообщения статуса (domofon/status)
* 'R' - ready; отправляется после успешной загрузки или в ответ на сообщение 'P'
* 'L' - last will message; отправляется когда устройство отключается от сети
## Входящие MQTT команды (domofon/in)
* 'O' - открыть дверь
* 'N' - отклонить вызов (дверь не откроется)
* 'P' - ping (ответом будет статус 'R')
## Информационные MQTT сообщения (domofon/out)
* 'C' - call; отправляется при входящем вызове
* 'H' - hangup; отправляется когда входящий вызов завершается
* 'B' - button; отправляется при открытии двери аппаратной кнопкой
* 'J' - reJected; отправляется когда вызов отклоняется аппаратной кнопкой
* 'S' - success; отправляется в ответ на команды 'O' и 'N'
* 'F' - fail; отправляется в ответ на команды 'O' и 'N' (означает, что команда была получена, но в данный момент не было входящего звонка)
## Интеграция с Home Assistant
![Home Assistant](https://raw.githubusercontent.com/Anonym-tsk/smart-domofon/master/native/homeassistant/ha.png)
[Конфигурация для Home Assistant с автоматизацией, сенсором и переключателями](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/homeassistant/domofon.yaml)
Положите этот файл в `/config/packages/domofon.yaml` и исправьте используемые сервисы в автоматизации.

68
native/README_EN.md Normal file
View File

@@ -0,0 +1,68 @@
# Smart intercom based on ESP8266
**English** | [Русский](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/README.md)
This device can send "door open" command to intercom main unit after receiving of incoming call. It only works with coordinate line intercoms. Tested with Cyfral russian intercom.
---
Enjoy my work? [Help me out](http://yasobe.ru/na/esphome) for a couple of :beers: or a :coffee:!
[![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](http://yasobe.ru/na/esphome)
---
## Parts used
* NodeMCU v3 x 1
* Double relay module x1 (or two single relay modules)
* LED x3 (or one RGB LED)
* Button x2
* Optocoupler x1
* Some resistors
Instead of NodeMCU, you can buy almost any ESP with enough count of GPIO (for example, Wemos D1 Mini). Button and LED are optional.
## Example Parts
1. [Оптопара PC817B](https://roboshop.spb.ru/PC817B)
2. [Реле электромеханическое 2-канальное](https://roboshop.spb.ru/SRD-05VDC-SL-C-2-channel-rele)
3. [NodeMCU V3](https://roboshop.spb.ru/NodeMCU-v3-dev-board)
4. [RGB светодиод на плате](https://roboshop.spb.ru/RGB-led-module)
5. [Кнопка тактовая 6х6х13мм KFC-A06-13H](https://roboshop.spb.ru/KFC-A06-13H)
6. [Провода "мама-мама" 10см, 20 шт.](https://roboshop.spb.ru/female-to-female-line)
7. [Провода "папа-мама" 10см, 20 шт.](https://roboshop.spb.ru/male-to-female-line)
8. [Резисторы](https://roboshop.spb.ru/600-resist-set)
9. [microUSB кабель](https://roboshop.spb.ru/BS-410)
## Scheme
![Scheme](https://raw.githubusercontent.com/Anonym-tsk/smart-domofon/master/native/scheme.jpeg)
*Thanks to Oleg Yu*
### This is old deprecated version. Please use new [ESPHome version](https://github.com/Anonym-tsk/smart-domofon/blob/master/esphome/README_EN.md)
## Configuration and build
1. Fill in WiFi and MQTT credentials in [software.h](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/src/config/software.h)
2. Fill in `upload_port` in [platformio.ini](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/platformio.ini)
3. Use [PlatformIO](https://platformio.org/platformio-ide) to build and upload firmware
## Status MQTT messages (domofon/status)
* 'R' - ready; sent after successfull boot-up or after receiving of 'P' message
* 'L' - last will message; send when device goes offline
## Incoming MQTT messages (domofon/in)
* 'O' - door open command
* 'N' - call reject command (door will not open)
* 'P' - ping command (answers with 'R')
## Outgoing MQTT messages (domofon/out)
* 'C' - call; sent after detecting of incoming intercom call
* 'H' - hangup; sent after detected incoming call finished
* 'B' - button; sent when "door open" has been performed by green hw button press
* 'J' - reJected; sent when incoming call has been rejected by red hw button press
* 'S' - success; sent in response to 'O' or 'N' command
* '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)
## Home Assistant integration
![Home Assistant](https://raw.githubusercontent.com/Anonym-tsk/smart-domofon/master/native/homeassistant/ha.png)
[Full configuration with sensor, switches and automations](https://github.com/Anonym-tsk/smart-domofon/blob/master/native/homeassistant/domofon.yaml)
Put this file into `/config/packages/domofon.yaml` and correct notification service in automations.

24
native/data/index.html Normal file
View 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
native/data/script.js Normal file
View 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
native/data/style.css Normal file
View 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;
}

View File

@@ -0,0 +1,133 @@
# Domofon
# Automatically open door input
input_boolean:
domofon_auto_open:
name: "Domofon auto open"
icon: mdi:door-open
domofon_auto_open_once:
name: "Domofon auto open once"
icon: mdi:door-open
# Incoming call sensor
binary_sensor:
- platform: mqtt
name: "Domofon"
state_topic: "domofon/out"
availability_topic: "domofon/status"
payload_on: "C"
payload_off: "H"
payload_available: "R"
payload_not_available: "L"
device_class: lock
automation:
- alias: Domofon incoming call notification
trigger:
platform: state
entity_id: binary_sensor.domofon
to: 'on'
condition:
condition: and
conditions:
- condition: state
entity_id: input_boolean.domofon_auto_open
state: 'off'
- condition: state
entity_id: input_boolean.domofon_auto_open_once
state: 'off'
action:
service: notify.telegram_all
data:
message: 'Звонок в домофон'
data:
inline_keyboard:
- "Открыть:/domofon_open, Отклонить:/domofon_reject"
- alias: Domofon incoming call notification (auto open)
trigger:
platform: state
entity_id: binary_sensor.domofon
to: 'on'
condition:
condition: or
conditions:
- condition: state
entity_id: input_boolean.domofon_auto_open
state: 'on'
- condition: state
entity_id: input_boolean.domofon_auto_open_once
state: 'on'
action:
- service: notify.telegram_all
data:
message: 'Звонок в домофон (откроется автоматически)'
- service: input_boolean.turn_off
data:
entity_id: input_boolean.domofon_auto_open_once
- service: mqtt.publish
data:
topic: "domofon/in"
payload: "O"
- alias: Telegram /domofon_open callback
trigger:
platform: event
event_type: telegram_callback
event_data:
data: '/domofon_open'
action:
- service: telegram_bot.answer_callback_query
data_template:
callback_query_id: "{{ trigger.event.data.id }}"
message: "Открываю..."
- service: mqtt.publish
data:
topic: "domofon/in"
payload: "O"
- alias: Telegram /domofon_reject callback
trigger:
platform: event
event_type: telegram_callback
event_data:
data: '/domofon_reject'
action:
- service: telegram_bot.answer_callback_query
data_template:
callback_query_id: "{{ trigger.event.data.id }}"
message: "Отклоняю..."
- service: mqtt.publish
data:
topic: "domofon/in"
payload: "N"
- alias: Domofon success notification
trigger:
platform: mqtt
topic: 'domofon/out'
payload: 'S'
action:
service: notify.telegram_all
data:
message: "Выполнено"
- alias: Domofon failure notification
trigger:
platform: mqtt
topic: 'domofon/out'
payload: 'F'
action:
service: notify.telegram_all
data:
message: "Ошибка! Нет входящего звонка"
- alias: Domofon opened by button notification
trigger:
platform: mqtt
topic: 'domofon/out'
payload: 'B'
action:
service: notify.telegram_all
data:
message: "Домофон открыт кнопкой"

BIN
native/homeassistant/ha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

33
native/platformio.ini Normal file
View 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

BIN
native/scheme.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View 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
native/src/config/mqtt.h Normal file
View 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_

View File

@@ -0,0 +1,25 @@
#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
#define RELAY_AFTER_OPEN_ON_TIME 500
#endif // SW_H_

12
native/src/debug.ino Normal file
View 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...);
}

146
native/src/domofon.ino Normal file
View File

@@ -0,0 +1,146 @@
#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();
delay(RELAY_AFTER_OPEN_ON_TIME);
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
native/src/hardware.ino Normal file
View 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);
}

13
native/src/inc/include.h Normal file
View File

@@ -0,0 +1,13 @@
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
#include <FS.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
native/src/inc/types.h Normal file
View 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
native/src/led.ino Normal file
View 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
native/src/main.ino Normal file
View 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
native/src/mqtt.ino Normal file
View 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);
DEBUG_F("[MQTT] Message sent: %s\n", msg);
}
void mqttSendStatus(const char *msg) {
mqttClient.publish(MQTT_TOPIC_STATUS, msg, true);
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, 1, 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
native/src/ota.ino Normal file
View 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
native/src/server.ino Normal file
View 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
native/src/wifi.ino Normal file
View 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();
}
}