Он слишком большой и страшный.
#include "esp_task_wdt.h"
#include <HardwareSerial.h>
#include "ESP32Servo.h"
#include "WiFi.h" // библиотека подключения к сети wi-fi
#include <PubSubClient.h> // Библиотека работы по протоколу mqtt
//============Плейер===============================================================================
#include <DFMiniMp3.h> // Библиотека работы с мелодиями dfplayer
class Mp3Notify;
typedef DFMiniMp3<HardwareSerial, Mp3Notify> DfMp3; // define a handy type using serial and our notify class
DfMp3 dfmp3(Serial1);// instance a DfMp3 object,
class Mp3Notify
{
public:
static void PrintlnSourceAction(DfMp3_PlaySources source, const char* action)
{
if (source & DfMp3_PlaySources_Sd)
{
Serial.print("SD Card, ");
}
if (source & DfMp3_PlaySources_Usb)
{
Serial.print("USB Disk, ");
}
if (source & DfMp3_PlaySources_Flash)
{
Serial.print("Flash, ");
}
Serial.println(action);
}
static void OnError([[maybe_unused]] DfMp3& mp3, uint16_t errorCode)
{
// see DfMp3_Error for code meaning
Serial.println();
Serial.print("Com Error ");
Serial.println(errorCode);
}
static void OnPlayFinished([[maybe_unused]] DfMp3& mp3, [[maybe_unused]] DfMp3_PlaySources source, uint16_t track)
{
Serial.print("Play finished for #");
Serial.println(track);
}
static void OnPlaySourceOnline([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
{
PrintlnSourceAction(source, "online");
}
static void OnPlaySourceInserted([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
{
PrintlnSourceAction(source, "inserted");
}
static void OnPlaySourceRemoved([[maybe_unused]] DfMp3& mp3, DfMp3_PlaySources source)
{
PrintlnSourceAction(source, "removed");
}
};
// Подключаем библиотеку русских сиволов
#define _LCD_TYPE 1 // Тип подключения дисплея: 1 - по шине I2C, 2 - десятиконтактное. Обязательно указывать ДО подключения библиотеки LCD_1602_RUS_ALL.h
#include "LiquidCrystal_I2C.h"
#include <LCD_1602_RUS_ALL.h>
LCD_1602_RUS lcd(0x3F, 16, 2);
//========= НАСТРОЙКИ =============================================================================
//======= адресная лента ==========================================================================
#include <Adafruit_NeoPixel.h> // Библиотека адресной ленты
#define PIN_WS2812B 19 // Пин подключения адресной ленты
#define NUM_PIXELS 5 // Колличество светодиодов в адресной ленте WS2812b, равно колличеству рюмок
Adafruit_NeoPixel ws2812b(NUM_PIXELS, PIN_WS2812B, NEO_GRB + NEO_KHZ800); // Создаем объект адресной ленты
//======= Wi-Fi и MQTT брокер =====================================================================
const char* ssid = "RT-GPON-89A0"; // имя сети wi-fi
const char* password = "*******"; // пароль сети wi-fi
// MQTT данные с wqtt.ru
const char* mqtt_server = "*******";
const int mqtt_port = 18170;
const char* mqtt_user = "*******";
const char* mqtt_password = "*******";
WiFiClient espClient;
PubSubClient client(espClient);
// Рэле включения от алисы
const String relay_topic = "drink";
const int RELAY = 18; // Временно выводим состояние mqtt брокера , потом нужно буде запускать процедуру по приходящим данным
bool relay_on = false;
#define BUSY_PIN 23 // пин готовности DF плеера низким, когда играет песня, и высоким, когда ничего не играет, не подключать на gpio12
Servo myservo; // create servo object to control a servo
uint32_t servo_time_work = 1000; // Время задержки перед включением помпы необходимое для поворота сервы
#define SERVO_ATTACH 13 // attaches the servo on pin 13 to the servo object
#define SHORT_PRESS_TIME 1000 // 1000 milliseconds
#define LONG_PRESS_TIME 1000 // 1000 milliseconds
#define SW_PIN 15 // пин GPIO15 ESP32 назначаем для кнопки SW энкодера
#define DT_PIN 2 // пин GPIO2 ESP32 назначаем для DT выхода энкодера
#define CLK_PIN 4 // пин GPIO4 ESP32 назначаем для CLK выхода энкодера
#define PUMP_PIN 14 // Пин GPIO14 ESP32 назначаем для помпы.
uint32_t pump_time_work = 3000; // Время работы помпы
const uint8_t SW_pins[] = {32, 33, 25, 26, 27}; // пины концевиков ESP32 для 5 стопок
const uint8_t Pos[] = {15, 45, 90, 130, 170, 0}; // массив,в котором хранятся позиции сервы, в последней переменной хранится позиция парковки
bool flag_menu_bar = false; // Флаг меню
//static PROGMEM const char - помещает переменную во флешпамять
static PROGMEM const char menu_bar0[] = "Меню";
static PROGMEM const char menu_bar1[] = "> Промывка <";
static PROGMEM const char menu_bar2[] = "> Налив <";
static PROGMEM const char menu_bar3[] = "> Настройка <";
static PROGMEM const char menu_bar4[] = "> Выход <";
//======= Таски ===================================================================================
//TaskHandle_t TaskVolume; // Задача регулирования грамкости
//======= Энкодер =================================================================================
// Обязательно на сигнальные провода энкодера вешать керамические конденсаторы 0.1мкф
#include "ESP32Encoder.h"
#include <ezButton.h> // Библиотека для использования SW пина энкодера
bool isPressing = false;
bool isLongDetected = false;
unsigned long pressedTime = 0; // Время нажатия на кнопку энкодера
unsigned long releasedTime = 0; // Время отпускания кнопки энкодера
ezButton button(SW_PIN); // создаем обьект ezButton на 15 пине;
// Обработчик прерываний
static IRAM_ATTR void enc_cb(void* arg) {
ESP32Encoder* enc = (ESP32Encoder*) arg;
}
ESP32Encoder encoder(true, enc_cb);
//============Таймер===============================================================================
//создадим указатель на переменную с именем encButton_timer и типом type hw_timer_t для того чтобы в последующем производить настройку таймера.
hw_timer_t *encButton_timer = NULL; // обьявляем переменную таймера
//======= Функция подключения к MQTT брокеру ======================================================
void reconnect() {
while (!client.connected()) {
Serial.print("Попытка подключения по протоколу MQTT...");
String clientId = "ESP32-" + WiFi.macAddress();
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password) ) {
Serial.println("Подключено");
client.subscribe( (relay_topic + "/#").c_str() ); // подписка на определенный топик
} else {
Serial.print("ошибка, rc=");
Serial.print(client.state());
Serial.println(" повторим попытку через 5 секунд");
delay(5000);
}
}
}
// Функция налива
void naliv() {
//периодический вызов функции df mp3.loop()
// позволяет обрабатывать уведомления без прерываний
//dfmp3.loop();
lcd.clear();
lcd.setCursor(3, 0); // Устанавливает курсор в (позицию,Строка)
lcd.print("Наливаем в");
lcd.setCursor(4, 1); // Устанавливает курсор в (позицию,Строка)
lcd.print("Рюмку");
for (int i = 0; i < 5; i++) { // опрашиваем концевики
if(!digitalRead(SW_pins[i])){
lcd.setCursor(10, 1); // Устанавливает курсор в (Позиция,Строка)
lcd.print(i+1, DEC); // Печатаем номер рюмки в которую льем
myservo.write(Pos[i]); // Предвигаем серву в позицию " i "
delay(servo_time_work); // Время задержки перед включением помпы
digitalWrite(PUMP_PIN, HIGH); // ВКЛЮЧАЕМ помпу
delay(pump_time_work); // время налива
digitalWrite(PUMP_PIN, LOW); // ВыКЛЮЧАЕМ помпу
delay(1000);
}
}
myservo.write(Pos[5]); // возвращаем серву в парковочную позицию
lcd.clear();
lcd.setCursor(5, 0);
lcd.print("Налито");
lcd.setCursor(3, 1);
lcd.print("Тост пошел");
dfmp3.nextTrack();
Serial.println(millis());
delay(700); // Обязательная задержка для опроса мр3 плейера
while (!digitalRead(BUSY_PIN)); // Дожидаемся окончания проигрывания тоста
lcd.setCursor(3, 1);
lcd.print("Приступаем");
}
void updateStatePins(void){ // временная процедура включения реле
if(relay_on){
digitalWrite(RELAY, HIGH);
Serial.println("вкл алиса");
naliv();
}else{
digitalWrite(RELAY, LOW);
Serial.println("алиса выключила лампу!");
}
}
//======= функция обработки которая вызывается в client.loop ======================================
void callback(char* topic, byte* payload, unsigned int length) {
String data_pay;
for (int i = 0; i < length; i++) {
data_pay += String((char)payload[i]);
}
Serial.println(data_pay);
if( String(topic) == relay_topic ){
if(data_pay == "ON" || data_pay == "1") relay_on = true;
if(data_pay == "OFF" || data_pay == "0") relay_on = false;
}
updateStatePins();
}
void IRAM_ATTR onTimer(){ // обрабочкик таймера
}
// Функция вывод на экран уровня громкости
void volum_level(bool step){
int i=0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Громкость");
lcd.setCursor(10, 0);
int volume = dfmp3.getVolume();
if (step) {
if (volume==30) lcd.print("МАКС");
else{
dfmp3.setVolume(volume+1); // Увеличивавем громкость динамика
lcd.print(volume+1, DEC); // Выводит на экран переменную в десятичном формате
}
}
else if (volume == 0) lcd.print("МИН"); // Выводит на экран символы цифр;
else {
dfmp3.setVolume(volume-1); // Уменьшаем громкость динамика
lcd.print(volume-1, DEC); // Выводит на экран переменную в десятичном формате
}
for ( i = 0; i < round( volume/2 ); i++)
{
lcd.setCursor(i,1);
lcd.write(255); // Выводит на экран символы по коду
}
}
// Функция опроса энкодера
int opros_encoder(){
int8_t otvet = 0;
button.loop(); // опрос кнопки энкодера
if(button.isPressed()){ // Кнопка энкодера нажата
pressedTime = millis();
isPressing = true;
isLongDetected = false;
}
if(button.isReleased()){ // Кнопка энкодера отпущена
isPressing = false;
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if ( pressDuration < SHORT_PRESS_TIME ){
Serial.println("Короткое нажатие зафиксировано");
otvet = 1;
}
}
if (isPressing == true && isLongDetected == false) {
long pressDuration = millis() - pressedTime;
if ( pressDuration > LONG_PRESS_TIME ) {
Serial.println("Длинное нажатие определено");
isLongDetected = true;
otvet = 2;
}
}
delay(10);
if (encoder.getCount()>0){ // Вращение вправо
//Serial.println("Повернули вправо");
otvet = 3;
}
else if (encoder.getCount()<0){ //Вращение влево
// Serial.println("Повернули влево");
otvet = 4;
}
encoder.setCount(0); // обнуляем энкодер
return(otvet);
}
//======= Промывка системы ========================================================================
// Функция запускает насос на пробный налив, итоговый результат налива неопределен
// в рюмку может быть налито сколько угодно
void flushSystem(){
// опросить концевики
// если нет рюмки
// выйти из промывки и сообщить , что нужна тара
// если рюмка стоит, то налить 50- мл.
// запустить какое нибудь прикольное сообщение
}
//======= Прогулка по меню вверх-вниз =============================================================
void menuUpDown(int numMenu){
}
// Функция обработки меню
void menu(){
// flag_menu_bar = true; // Поднимаем флаг меню
lcd.clear();
lcd.setCursor(6, 0); // Устанавливает курсор в (позицию,Строка)
lcd.print(F(menu_bar0));
lcd.setCursor(0, 1); // Устанавливает курсор в (позицию,Строка)
lcd.print(menu_bar1);
int i =0;
while (i!=2)
{
switch (i) {
case 0:
// выполнить, если значение == 0
// запустить таймер выхода из меню
break;
case 1:
// выполнить, если значение == 1
Serial.println("Короткое нажатие");
// считать состояние
break;
case 2:
// выполнить, если значение == 2
break;
case 3:
// выполнить, если значение == 3
{
Serial.println(" отработали козу Повернули вправо");
// lcd.setCursor(6, 0); // Устанавливает курсор в (позицию,Строка)
// lcd.print(F(menu_bar0));
lcd.setCursor(0, 1); // Устанавливает курсор в (позицию,Строка)
lcd.print(F(menu_bar2));
}
break;
case 4:
// выполнить, если значение == 4
{
Serial.println(" отработали козу Повернули влево");
// lcd.setCursor(6, 0); // Устанавливает курсор в (позицию,Строка)
// lcd.print(F(menu_bar0));
lcd.setCursor(0, 1); // Устанавливает курсор в (позицию,Строка)
lcd.print(F(menu_bar1));
}
break;
}
i = opros_encoder();
}
// flag_menu_bar = false; // Опускаем флаг
lcd.clear();
lcd.setCursor(3, 0); // Первый аргумент позиция, второй строка
lcd.print("Наливатор");
lcd.setCursor(4, 1);
lcd.print("Версия 1");
}
//======= Функция светомозыки =====================================================================
void CvetoMuzik() {
while (!digitalRead(BUSY_PIN)){ // Пока проигрывает музыка выполняем
// Rainbow cycle along whole ws2812b. Pass delay time (in ms) between frames.
// Hue of first pixel runs 3 complete loops through the color wheel.
// Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
// means we'll make 3*65536/256 = 768 passes through this outer loop:
for(long firstPixelHue = 0; firstPixelHue < 3*65536; firstPixelHue += 256) {
for(int i=0; i<ws2812b.numPixels(); i++) { // For each pixel in ws2812b...
// Offset pixel hue by an amount to make one full revolution of the
// color wheel (range of 65536) along the length of the ws2812b
// (ws2812b.numPixels() steps):
int pixelHue = firstPixelHue + (i * 65536L / ws2812b.numPixels());
// ws2812b.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
// optionally add saturation and value (brightness) (each 0 to 255).
// Here we're using just the single-argument hue variant. The result
// is passed through ws2812b.gamma32() to provide 'truer' colors
// before assigning to each pixel:
ws2812b.setPixelColor(i, ws2812b.gamma32(ws2812b.ColorHSV(pixelHue)));
}
ws2812b.show(); // Update ws2812b with new contents
delay(1); // Pause for a moment
}
}
}
//======= Функция которая будет выполнятся при работе задачи регулирования громкости
//======= Опрос концевиков ========================================================================
// Опрашивает концевики и синий цвет если рюмки нет, красный цвет если рюмка стоит
void opros_Pins(){
for (int i = 0; i < 5; i++) { // опрашиваем концевики
ws2812b.setPixelColor(i, ws2812b.Color(0, 0, 250)); // указываем синий цвет пикселя
if(!digitalRead(SW_pins[i])){
ws2812b.setPixelColor(i, ws2812b.Color(250, 0, 0)); // указываем красный цвет пикселя
}
}
ws2812b.show(); // Передаем состояние пикселей в ленту
}
void setup() {
Serial.begin(115200);
//===== Подключение к сети Wi-Fi ================================================================
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Подключение к WiFi..");
}
Serial.println("Подключились к сети WiFi");
//===== Реле включения от Алисы и настройки mqtt брокера ========================================
pinMode(RELAY, OUTPUT); // Пин реле как выход
digitalWrite(RELAY, LOW); // Состояние выхода в "0"
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
//===== Помпа ===================================================================================
pinMode(PUMP_PIN, OUTPUT); // настроем пин помпы в качестве выходного вывода
digitalWrite(PUMP_PIN, LOW); // ВЫКЛЮЧАЕМ помпу
//===== Таски ===================================================================================
//===== Лента адресных светодиодов ==============================================================
ws2812b.begin(); // инициализация адресной ленты WS2812B
pinMode(PIN_WS2812B, OUTPUT);
//===== Серва ===================================================================================
myservo.attach(SERVO_ATTACH);
myservo.write(Pos[5]);
//===== Экран ===================================================================================
lcd.init(); //Инициализация LCD (по умолчанию для ESP32 30Pin: 21 - SDA, 22 - SCL)
lcd.backlight();
lcd.setCursor(3, 0); // Первый аргумент позиция, второй строка
lcd.print("Наливатор");
lcd.setCursor(4, 1);
lcd.print("Версия 1");
//===== Энкодер =================================================================================
encoder.attachSingleEdge(DT_PIN, CLK_PIN);
encoder.clearCount();
encoder.setFilter(1023);// Задержка для фильтрации дребезга. На медленных чипах нужно уменьшать
// esp_task_wdt_add(loopTaskHandle);
button.setDebounceTime(50); // устанавливаем время восстановления 50 миллисекундам
//======= Концевики ===============================================================================
for (int i = 0; i < 5; i++) { // установить концевики в положение высокого уровня
pinMode(SW_pins[i], INPUT_PULLUP); // настроем пины рюмок в качестве входного вывода и включим внутренний подтягивающий резистор
}
//========Плейер=================================================================================
pinMode(BUSY_PIN, INPUT);
dfmp3.begin(16,17);// UART0 является основным UART на ESP32 и по умолчанию подключается к пинам GPIO1 (TX0) и GPIO3 (RX0).
// Он часто используется для связи с компьютером через серийный монитор и также используется для прошивки платы ESP32 новыми программами.
//Сообщения могут выводиться в консоль с помощью Serial.println().
// UART1 Не разведен и используется для связи с помятью
// Используем UART2 на пинах RX 16 и TX 17
// for boards that support hardware arbitrary pins
// dfmp3.begin(10, 11); // RX, TX
// during development, it's a good practice to put the module
// into a known state by calling reset().
// You may hear popping when starting and you can remove this
// call to reset() once your project is finalized
dfmp3.reset();
//uint16_t version = dfmp3.getSoftwareVersion();
//Serial.print("version ");
//Serial.println(version);
// show some properties and set the volume
//uint16_t volume = dfmp3.getVolume();
dfmp3.setVolume(5); //Установка громкости динамика
//uint16_t count = dfmp3.getTotalTrackCount(DfMp3_PlaySource_Sd); // колличсество треков на флешке
//Serial.print("files ");
//Serial.println(count);
// uint16_t mode = dfmp3.getPlaybackMode();
// Serial.print("playback mode ");
// Serial.println(mode);
// Serial.println("starting...");
dfmp3.playMp3FolderTrack(1);
delay(800); // Обязательная задержка для опроса мр3 плейера
CvetoMuzik(); // Играем огнями пока звучит приветственная музыка
ws2812b.clear();
ws2812b.show();
}
void loop() {
if (!client.connected()) { // Постояння проверка подключение к mqtt брокеру
reconnect();
}
client.loop();
opros_Pins();
switch (opros_encoder()) {
case 0:
// выполнить, если значение == 0
break;
case 1:
// выполнить, если значение == 1
Serial.println("Короткое нажатие");
naliv() ; // Запускаем налив
break;
case 2:
// выполнить, если значение == 2
Serial.println("Длинное нажатие");
{
menu(); // Переходим в меню
}
break;
case 3:
// выполнить, если значение == 3
Serial.println(" отработали козу Повернули вправо");
volum_level(1);
break;
case 4:
// выполнить, если значение == 4
Serial.println(" отработали козу Повернули влево");
volum_level(0);
break;
}
}