Гоните в шею помощника, который гадит в код!
И вообще, не привыкайте перекладывать ответственность на кого-то, код Ваш - значит Вам определять какие в нём комментарии и Вам за них отвечать.
Гоните в шею помощника, который гадит в код!
И вообще, не привыкайте перекладывать ответственность на кого-то, код Ваш - значит Вам определять какие в нём комментарии и Вам за них отвечать.
При правильной организации списка нумерация в принципе не нужна
А если средствами ООП меню решать, то и списки не нужны.
согласен послушать ваши предложения и готов переписать под любой вариант.
я пытался написать такую функцию,которая не зависела бы от вложений . Пишешь только список ,как оглавление, а в функцию лезть не надо.
Дофига придется слушать. Я не готов столько говорить. Пересказывать книжки не мой конёк. Вот готовое примитивное меню. Я на нем постигал основы ООП, пока учил язык. Динамическое меню, то-есть контекстно-зависимое. Есть и серьезный косяк. Мне там нужен был вектор, и я ничего умнее на тот момент не придумал, чем использовать QList.
Кстати, итератор - экземпляр этого же класса.
#include "QList.h"
class Item {
public:
Item(Item* parent= nullptr) {_parent = parent;}
const char* _name {"Main"}; // name of current menu item
Item* _parent = nullptr; // parents pointer. Nullptr for root
uint8_t _retFrom {0}; // number of child-item, from witch was "go to parent" executed. first (0) by default
QList <Item*> child; // children refs list
size_t childCount {0}; // children qty
uint8_t _nrAtParentsList {0}; // items number at parent's child-list
Item* newChild(const char* nme, void (*ptr)()); // create a new child end-point with action
void (*actionF)() {nullptr}; // pointer to executable routine
private:
};
Item* Item::newChild(const char* nme, void (*ptrA)() = nullptr) {
Item* p = new(Item);
p->_name = nme;
p->_parent = this;
p->_nrAtParentsList = childCount++;
p->actionF = ptrA;
child.push_back(p);
return p;
}
Как видишь, код не сильно длинный
И да, я знаю, на деструкторы я забил))
так в библиотеке более 1000 строк кода… ее ж запихать нужно , а память она не бесконечная…
Я и говорю, это косяк. Более того, в 2.ХХ она не компилируется больше. Но суть та же. Вектор свой можешь написать. Мне без надобности, у меня нигде физическое управление не используется, только mqtt
И да, поржал с 1000 строк и объема памяти)) Ты прям так и думаешь, что там вся программа так записывается? Прям СЛОВАМИ? )))))))))
Напиши вектор на 100 строк кода
Сильно не ругайте.
#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 = "##########"; // имя сети wi-fi
const char* password = "#########"; // пароль сети wi-fi
// MQTT данные с wqtt.ru
// const char* mqtt_server = "m8.wqtt.ru";
// 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}; // массив,в котором хранятся позиции сервы, в последней переменной хранится позиция парковки
//#define SIZE_PRIMARY_MENU 4 // Указать колличество пунктов меню считая с "0"
//static PROGMEM const char так надо записать - помещает переменную во флешпамять
// static PROGMEM const char* menu_bar[] = {
// " МЕНЮ ",
// "> Промывка <",
// "> Налив <",
// "> Настройка <",
// "> Выход <"
// };
//uint8_t currentMenuItem = 0; // Текущий пункт меню
#define T_FOLDER 0 // Тип меню - папка
#define T_APP 1 // Функция для выполнения кода
struct menuStruct { // создаём ярлык структуры меню
uint8_t type; // тип меню папка или функция , если функция , то mode1 - номер функции, mode2 - параметры функций
uint8_t parent; // номер родителя, верхний уровень 0.
uint8_t mode1; // стартовый номер дочернего пункта меню в массиве MenuTable
uint8_t mode2; // колличество пунктов меню
char name[32]; // название пункта меню
};
//массив структуры меню
static PROGMEM menuStruct MenuTable[] = {
T_FOLDER, 0, 1, 3," МЕНЮ ", // Меню 0 Это MenuLevel
T_FOLDER, 0, 2, 3,"> Промывка <", // 1
T_APP, 1, 2, 3,">Старт промывки<", // 2
T_APP, 1, 2, 3,"> Стоп промывки<", // 3
T_APP, 1, 2, 3,"> Выход <", // 4
T_FOLDER, 0, 6, 5,"> Настройка <", // 5
T_FOLDER, 5, 6, 2,"> Громкость <",
T_APP, 6, 6, 2,">Уровень умолч.<",
T_APP, 6, 6, 2,"> Выход <",
T_FOLDER, 5, 10,3,"> Налив <",
T_APP, 6, 1, 3, "Уровень",
T_APP, 6, 2, 3, "Проверка",
T_APP, 6, 3, 3, "Выход",
T_FOLDER, 5, 14,3,"> Тип тостов <",
T_APP, 6, 1, 3, "Случайно",
T_APP, 6, 2, 3, "По порядку",
T_APP, 6, 3, 3, "Выход",
T_FOLDER, 5, 18,3,"> Тип налива <",
T_APP, 6, 1, 1, "Стандартный",
T_APP, 6, 2, 1, "Игра",
T_APP, 6, 3, 1,"> Выход <",
T_APP, 5, 1, 1,"> Выход <",
T_APP, 0, 2, 1,"> Выход <"
};
uint8_t menuLevel = 0; // Текущий уровень меню
//======= Таски ===================================================================================
//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 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 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 плейера
CvetoMuzik(); // воспроизводим светомузыку пока идет тост
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 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 < ceil( volume/2 ); i++) // округление до большего числа вверх
//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);
int i=encoder.getCount();
if (i>0){ // Вращение вправо
//Serial.println("Повернули вправо");
otvet = 3;
}
else if (i<0){ //Вращение влево
// Serial.println("Повернули влево");
otvet = 4;
}
encoder.clearCount();
//encoder.setCount(0); // обнуляем энкодер
return(otvet);
}
//======= Промывка системы ========================================================================
// Функция запускает насос на пробный налив, итоговый результат налива неопределен
// в рюмку может быть налито сколько угодно
void flushSystem(){
myservo.write(Pos[0]); // Предвигаем серву в позицию " 0 "
if(digitalRead(SW_pins[0])){ // опрашиваем нулевой концевик
delay(servo_time_work); // Время задержки перед включением помпы
digitalWrite(PUMP_PIN, HIGH); // ВКЛЮЧАЕМ помпу
delay(pump_time_work); // время налива
digitalWrite(PUMP_PIN, LOW); // ВыКЛЮЧАЕМ помпу
}
return; // Если рюмка не стоит или пролили систему, то выходим
// опросить концевики
// если нет рюмки
// выйти из промывки и сообщить , что нужна тара
// если рюмка стоит, то налить 50- мл.
// запустить какое нибудь прикольное сообщение
}
//======= Аварийная остановка промывки ============================================================
void stopFlushSystem(){
digitalWrite(PUMP_PIN, LOW); // ВыКЛЮЧАЕМ помпу
return;
}
//======= Выход из меню ===========================================================================
void menuExit(){
menuLevel = MenuTable[menuLevel].parent; // Устанавливает номер родительского меню в массиве меню;
}
//======= Выполнение команды меню =================================================================
void menuCommand(int command){
switch (command)
{
case 2: // Промывка
flushSystem();
break;
case 3: // Аварийная остановка промывки
stopFlushSystem();
break;
case 4: // Выход из меню
menuExit();
break;
// default:
// break;
}
return;
}
//======= Функция обработки меню ==================================================================
void menuEnter(int command){ // command - это номер родителя меню
menuLevel = MenuTable[command].mode1; // Устанавливает номер дочернего меню в массиве меню
int i =0;
int timeavtoexitstart = millis();
lcd.clear() ;
u_int8_t currentMenuItem = 1; // Устанавливает номер текущего пункта меню
while ((millis() - timeavtoexitstart < 60000) || i==2) // автовыход из пунктов меню через 1 минуту
{
lcd.setCursor(0, 0); // Устанавливает курсор в (позицию,Строка)
lcd.print(MenuTable[command].name);
lcd.setCursor(0, 1); // Устанавливает курсор в (позицию,Строка)
lcd.print(MenuTable[menuLevel].name);
i = opros_encoder();
switch (i) {
case 0:
// выполнить, если значение == 0
break;
case 1:
// выполнить, если значение == 1
{
if (MenuTable[menuLevel].type==0)
{
int menuLevelOld = menuLevel;
menuEnter(MenuTable[menuLevel].mode1-1); // передали в функцию меню номер родителя
menuLevel = menuLevelOld;
}
if (MenuTable[menuLevel].type==1)
{
// Выполнить функцию привязанную к данному меню
menuCommand(menuLevel);
return;
}
}
break;
case 2:
// выполнить, если значение == 2
break;
case 3:
// выполнить, если значение == 3
{ //Serial.println(" отработали козу Повернули вправо шаг меню вниз");
currentMenuItem++;
if (currentMenuItem>MenuTable[command].mode2)
{
currentMenuItem --;
menuLevel --;
}
do {
menuLevel++;
} while (command!=MenuTable[menuLevel].parent);
timeavtoexitstart = millis();
}
break;
case 4:
// выполнить, если значение == 4
{ //Serial.println(" отработали козу Повернули влево шаг меню вверх");
currentMenuItem--;
if (currentMenuItem<1)
{
currentMenuItem ++;
menuLevel ++;
}
do {
menuLevel--;
} while (command!=MenuTable[menuLevel].parent);
timeavtoexitstart = millis();
}
break;
}
}
lcd.clear();
lcd.setCursor(3, 0); // Первый аргумент позиция, второй строка
lcd.print("Наливатор");
lcd.setCursor(4, 1);
lcd.print("Версия 1");
menuLevel = command; // Устанавливает уровень меню в начальное состояние
}
//======= Опрос концевиков ========================================================================
// Опрашивает концевики и синий цвет если рюмки нет, красный цвет если рюмка стоит
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(2); //Установка громкости динамика
//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();
int i = opros_encoder();
switch (i) {
// case 0:
// выполнить, если значение == 0
// break;
case 1:
// выполнить, если значение == 1 Это короткое нажатие на энкодер
//Serial.println("Короткое нажатие");
naliv() ; // Запускаем налив
break;
case 2:
// выполнить, если значение == 2 Это длинное нажатие на энкодер
//Serial.println("Длинное нажатие");
{
//menu(); // Переходим в меню
menuEnter(0); // выполнить функцию привязанной к данному меню
}
break;
case 3:
// выполнить, если значение == 3 Это поворот вправо
//Serial.println(" отработали козу Повернули вправо");
volum_level(1);
break;
case 4:
// выполнить, если значение == 4 Это поворот влево
//Serial.println(" отработали козу Повернули влево");
volum_level(0);
break;
}
}
Да, тяжко тебе будет. Никто же еще (из оставивших ранее комментарии) даже не пытался «ругать»…
Началось с
а теперь
вот я всегда знал, что “покритикуйте” в посте, на деле означает “похвалите”. Как только что-то иное, сразу проблемы
хвалить не надо, хочется конструктивной критики, например там то и там будет косяк. Или почитай того то, там написано то то… Вопрс только по функции меню, ни по чем другому. Я то не программист, в основном электронщик, больше с железками .
Да, я уж сколько раз раз пробовал. Но у вашего брата – вопрошающего, обычно бывает такая тонкая душевная организация, только чуть не то слово подберёшь – обиды и срач на весь форум. Кроме того, на любое “если делать универсально, то можно будет потом “и то и это”” тут же следует ответ – мне всего этого не надо, ты по делу говори и начинаешь теряться, что же там, блин, “по делу”, а что – нет.
Если правда хотите серьёзного разговора, я готов ещё раз попробовать, но давайте сделаем нормальную программу для этого – только меню и пустые заглушки для исполнительных функций (они просто печатают в сериал “выбран такой-то пункт меню”), иначе программа огромная, зачем на “вся эта цветомузыка” и сотни строк работы с нею?
Хотите – попробуем, но при первых обидках и оправданиях, я сливаюсь – неинтересно (можно засчитывать слив). Если будете пробовать, для начала уберите из программы все “исполнительные” функции – замените заглушками и выложите.
Вот так?
// Подключаем библиотеку русских сиволов
#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);
#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 T_FOLDER 0 // Тип меню - папка
#define T_APP 1 // Функция для выполнения кода
struct menuStruct {
uint8_t type; // тип меню папка или функция , если функция , то mode1 - номер функции, mode2 - параметры функций
uint8_t parent; // номер родителя, верхний уровень 0.
uint8_t mode1; // стартовый номер дочернего пункта меню в массиве MenuTable
uint8_t mode2; // колличество пунктов меню
char name[32]; // название пункта меню
};
//массив структуры меню
static PROGMEM menuStruct MenuTable[] = {
T_FOLDER, 0, 1, 3," МЕНЮ ", // Меню 0 Это MenuLevel
T_FOLDER, 0, 2, 3,"> Промывка <", // 1
T_APP, 1, 2, 3,">Старт промывки<", // 2
T_APP, 1, 2, 3,"> Стоп промывки<", // 3
T_APP, 1, 2, 3,"> Выход <", // 4
T_FOLDER, 0, 6, 5,"> Настройка <", // 5
T_FOLDER, 5, 6, 2,"> Громкость <",
T_APP, 6, 7, 2,">Уровень умолч.<",
T_APP, 6, 7, 2,"> Выход <",
T_FOLDER, 5, 10,3,"> Налив <",
T_APP, 6, 10,3, "Уровень",
T_APP, 6, 10,3, "Проверка",
T_APP, 6, 10,3, "Выход",
T_FOLDER, 5, 14,3,"> Тип тостов <",
T_APP, 6, 14,3, "Случайно",
T_APP, 6, 14,3, "По порядку",
T_APP, 6, 14,3, "Выход",
T_FOLDER, 5, 18,3,"> Тип налива <",
T_APP, 6, 18,3, "Стандартный",
T_APP, 6, 18,3, "Игра",
T_APP, 6, 18,3,"> Выход <",
T_APP, 5, 21,1,"> Выход <",
T_APP, 0, 22,1,"> Выход <"
};
uint8_t menuLevel = 0; // Текущий уровень меню
//======= Энкодер =================================================================================
// Обязательно на сигнальные провода энкодера вешать керамические конденсаторы 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);
// Функция опроса энкодера
// Возвращает: 0 - нет нажатия, 1 - короткое нажатие, 2 - длинное нажатие,
// 3 - поворот вправо, 4 - поворот влево
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);
int i=encoder.getCount();
if (i>0){ // Вращение вправо
Serial.println("Повернули вправо");
otvet = 3;
}
else if (i<0){ //Вращение влево
Serial.println("Повернули влево");
otvet = 4;
}
encoder.clearCount();
return(otvet);
}
//======= Выполнение команды меню =================================================================
// функция принимает числовое значение команды и отрабатывает в зависимости от команды
// привязанную к нему функцию
void menuCommand(int command){
switch (command)
{
case 2: // Промывка
Serial.println("Функция Промывка 2 позиция в таблице меню");
break;
case 3: // Аварийная остановка промывки
Serial.println("Функция Аварийная остановка промывки 3 позиция в таблице меню");
break;
case 4: // Выход из меню
Serial.println("Функция Выход из меню 4 позиция в таблице меню");
break;
//......
//......
//......
case 21: //выход из меню
Serial.println("Функция Выход из меню 21 позиция в таблице меню");
break;
// Ну и так далее
}
return;
}
//======= Функция обработки меню ==================================================================
void menuEnter(int command){ // command - это номер родителя меню
menuLevel = MenuTable[command].mode1; // Устанавливает номер дочернего меню в массиве меню
int i =0;
int timeavtoexitstart = millis(); // Время начала автовыхода
lcd.clear() ;
u_int8_t currentMenuItem = 1; // Устанавливает номер текущего пункта меню
while ((millis() - timeavtoexitstart < 60000) || i==2) // автовыход из пунктов меню через 1 минуту
{
lcd.setCursor(0, 0);
lcd.print(MenuTable[command].name); // Выводим название родительского меню
lcd.setCursor(0, 1);
lcd.print(MenuTable[menuLevel].name);// Выводим название дочернего меню
i = opros_encoder();
switch (i) {
case 1:
{
if (MenuTable[menuLevel].type==0) // Если меню с подпунктами
{
int menuLevelOld = menuLevel; // запоминаем текущий уровень меню
menuEnter(MenuTable[menuLevel].mode1-1); // передали в функцию меню номер родителя
menuLevel = menuLevelOld; // возвращаем текущий уровень меню
}
if (MenuTable[menuLevel].type==1) // Если меню без подпунктов
{
menuCommand(menuLevel); // Выполнить функцию привязанную к данному меню
return;
}
}
break;
case 2:
// Можно что-то выполнить, если длительное нажатие на энкодер
break;
case 3:
// поворот энкодера вправо увеличивает номер меню, если достигла последнего пункта меню,
// то поворот вправо не изменяет уровень меню
// если время автовыхода истекло, то выходим в меню родителя
{
currentMenuItem++;
if (currentMenuItem>MenuTable[command].mode2)
{
currentMenuItem --;
menuLevel --;
}
do {
menuLevel++;
} while (command!=MenuTable[menuLevel].parent);
timeavtoexitstart = millis();
}
break;
case 4:
// поворот энкодера влево уменьшает номер меню, если достиг верхнего пункта меню,
// то поворот вправо не изменяет уровень меню
// если время автовыхода истекло, то выходим в меню родителя
{
currentMenuItem--;
if (currentMenuItem<1)
{
currentMenuItem ++;
menuLevel ++;
}
do {
menuLevel--;
} while (command!=MenuTable[menuLevel].parent);
timeavtoexitstart = millis();
}
break;
}
}
lcd.clear();
lcd.setCursor(3, 0); // Первый аргумент позиция, второй строка
lcd.print("Наливатор");
lcd.setCursor(4, 1);
lcd.print("Версия 1");
menuLevel = command; // Устанавливает уровень меню в начальное состояние
}
void setup() {
Serial.begin(115200);
//===== Экран ===================================================================================
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 миллисекундам
}
void loop() {
int i = opros_encoder(); // опрос энкодера в основном цикле имеет 4 варианта
// 1 - короткое нажатие, 2 - длинное нажатие, 3 - поворот вправо, 4 - поворот влево
switch (i) {
case 1:
//naliv() ; // Запускаем налив
Serial.println("короткое нажатие на энкодер в основном цикле выполняет функцию налива");
delay(1000);
break;
case 2:
Serial.println("Длинное нажатие на энкодер в основном цикле выполняет функцию переходав меню выбора");
{
menuEnter(0);
}
break;
case 3:
Serial.println("Поворот вправо энкодер в основном цикле выполняет функцию увеличения громкости");
delay(1000);
break;
case 4:
Serial.println("Поворот влево энкодер в основном цикле выполняет функцию ууменьшения громкости");
delay(1000);
break;
}
}
micromenu и micromenu v2 погугли и посмотри как реализовано. Лаконичнее v2 не встречал. GitHub - abcminiuser/micromenu-v2: Tiny text-orientated menu library in C for embedded use.
Я так и не понял зачем все эти идентификаторы непонятные, с количеством пунктов, подменю, папка/не папка и т.д
Сам делаю как то так:
/*Полный набор из тех пунктов, которые могут быть нужны.
Переходы (следующий/предыдущий пункты)
Вложения (родитель/ребенок)
Возможность наличия переменной.
Действие по кнопке ОК.
Вызов функции.
menu_id пункт необязательный.
*/
typedef struct menuItem{
int menu_id;
char *menu_name;
struct menuItem *parent;
struct menuItem *child;
struct menuItem *next;
struct menuItem *prev;
struct menuItem *ok;
void (*Handler)();
int *value;
};
/*
Далее все пункты объявляем.
Если нужен переход указываем куда.
То же самое на функции и переменные.
Все что не надо запрещаем -затыкаем NULL
*/
struct menuItem
mOption,
mMain,
mSelect,
mTemperature,
mSelectValue
;
mMain={1,"MAIN ",NULL,NULL,&mOption,&mTemperature,&mMain,blink,NULL};
mOption={2,"OPTION ",NULL,NULL,&mSelect,&mMain,NULL,NULL,NULL};
mSelect={3,"SELECT ",NULL,NULL,&mTemperature,&mOption,&mSelectValue,NULL,NULL};
mTemperature={4,"TEMP ",NULL,NULL,&mMain,&mSelect,NULL,NULL,NULL};
mSelectValue={31,"VALUE ",&mSelect,NULL,NULL,NULL,&mSelect,NULL,&selectValue};
void loop(){
static menuItem *cMenu=&mMain;
}
Далее просто прописываем для каждого из возможных действий свой обработчик.
Примерно вот так идёт обработка энкодера.(У меня свое видение, энкодер возвращает -1/+1 в зависимости от того, куда его вращают)
if(encTick){
switch(encTick){
case 1:
if(cMenu->next)
cMenu=cMenu->next;
else{
if(cMenu->value)
*cMenu->value+=encTick;
}
break;
case -1:
if(cMenu->next)
cMenu=cMenu->prev;
else{
if(cMenu->value)
*cMenu->value+=encTick;
}
break;
}
updateFlag=1;
encTick=0;
}
if(encBtn.read()==1){
Serial.println("btn pressed");
if(cMenu->ok){
cMenu=cMenu->ok;
updateFlag=1;
} else
if(cMenu->Handler){
cMenu.Handler();
}
}
Как то так.
В итоге имеем меню, с возможностью перехода из любого места в любое, привязку переменных и функций.
Количество пунктов меню можно менять, корректировать переходы, при этом обработчики будут всегда одни.
Указатели для меня еще сложно. Пробую .
@smagluk, 36 байтов на один пункт меню - это круто. посмотрите это:
Сейчас уже не помню, но там что-то порядка одного байта на пункт меню при том, что меню формируется динамически в процессе выполнения программы. Другими словами, может быть перестроено.