Продолжаю мучать машинку-управляемую через MQTT. Есть проблема: вис системы - если стучать по всем клавишам подряд
- Войдите на сайт для отправки комментариев
Всем доброго, друзья!
Может кто подскажет: мучаю свою многострадальную машинку-управляемую с сайта через MQТТ-брокер (веб интерфейс шлет сообщения на брокер-откуда их забирает и исполняет машинка).
Веб интерфейс проверен-всё ок. С брокером тоже всё ок-все работает отлично. Где то косяк на этапе принятия сообщения и его дальнейшей обработки(я так подозреваю).
В чем косяк: в целом всё работает ок. Но, в какой то момент-машинка "зависает" с включенными колесами или наоборот-выключенными. На команды с сайта не реагирует. Машинка построена на ESP32 (я уже выкладывал ее). С батареей всё ок.
Вот так вылглядит код. Может кто подскажет, где теоретически может теряться принятое сообщение, чтобы машинка "зависла" в неопределенном состоянии?
Из кода вырезал: 1) код выравнивания скоростей колес-с помощью прерываний и PID-регулятора (чтобы машинка ехала строго вперед); 2) замер напряжения батареи 18650. Так как особого отношения к делу он не имеет.
Основной код:
#include <WiFi.h> #include <PubSubClient.h> #include <Pangodream_18650_CL.h> //#include <GyverPID.h> //---------------Замер напряжения батареи----------------- //#define ADC_PIN 34 //#define CONV_FACTOR 1.7 //#define READS 20 Pangodream_18650_CL BL; /** * If you need to change default values you can use it as * Pangodream_18650_CL BL(ADC_PIN, CONV_FACTOR, READS); */ volatile uint32_t charge_test = 0; // время последней проверки уровня заряда батареи //-------------------------------------------------------- // вставляем ниже SSID и пароль для своей WiFi-сети: const char* ssid = "сюда название WIFI сети"; const char* password = "пароль WIFI сети"; const char* mqtt_server = "адрес mqtt-брокера"; #define mqtt_port 1883 #define MQTT_USER "" //сюда имя пользователя MQTT, если система должна иметь пользователя и пароль #define MQTT_PASSWORD "" //сюда пароль пользователя MQTT, если система должна иметь пользователя и пароль #define MQTT_SERIAL_PUBLISH_CH "vasyapupgen|moshino" #define MQTT_SERIAL_RECEIVER_CH_1 "vasyapupgen/left" #define MQTT_SERIAL_RECEIVER_CH_2 "vasyapupgen/right" #define MQTT_SERIAL_RECEIVER_CH_3 "vasyapupgen/forward" #define MQTT_SERIAL_RECEIVER_CH_4 "vasyapupgen/reverse" #define MQTT_SERIAL_RECEIVER_CH_5 "vasyapupgen/ctrl" // переменная для хранения HTTP-запроса: String header; // мотор 1: int motor1Pin1 = 21; int motor1Pin2 = 19; //int enable1Pin = 14; // мотор 2: int motor2Pin1 = 23; int motor2Pin2 = 22; //int enable2Pin = 32; // переменные для свойств широтно-импульсной модуляции (ШИМ) 1-двигателя: const int freq = 30000; const int pwmChannel = 0; const int resolution = 8; int dutyCycle = 0; // переменные для свойств широтно-импульсной модуляции (ШИМ) 2-двигателя: const int freq2 = 30000; const int pwmChannel2 = 1; const int resolution2 = 8; int dutyCycle2 = 0; // переменные для расшифровки HTTP-запроса GET: String valueString = String(5); int pos1 = 0; int pos2 = 0; //------------------- Работа с оптическими датчиками -------------------------- int OnOff = 0; //сюда будет писаться, нажата ли клавиша в данный момент boolean CtrlState = false; String state = "Stop"; //сюда будет писаться состояние, т.е. куда едет машинка в данный момент volatile uint32_t lasttime1 = 0; volatile uint32_t lasttime2 = 0; volatile uint32_t last_check_time = 0; //время последнего вывода экран-результатов замера скорости вращения двигателей volatile uint32_t ONEmotor_speed = 0; //средняя скорость вращения 1 двигателя volatile uint32_t ONEmotor_speed_last = 0; //прежняя средняя скорость вращения 1 двигателя volatile uint32_t TWOmotor_speed = 0; //средняя скорость вращения 2 двигателя volatile uint32_t TWOmotor_speed_last = 0; //прежняя средняя скорость вращения 2 двигателя volatile uint32_t impulse_counter1 = 0; //счетчик импульсов 1 колеса volatile uint32_t impulse_counter2 = 0; //счетчик импульсов 2 колеса // boolean LastInterruptionState = false; //значение последнего прерывания volatile boolean InterruptionState = false; //значение последнего прерывания volatile boolean InterruptionState2 = false; //значение последнего прерывания String printing = "произошло прерывание1"; String printing2 = "произошло прерывание2"; volatile int PinState; //Куда подключены оптические датчики энкодера: int SensorPin1 = 13; int SensorPin2 = 14; //----------------------------------------------------------------------------- //----------------------БЛОК РАБОТЫ С MQTT и WIFI -------------------- WiFiClient wifiClient; PubSubClient client(wifiClient); void setup_wifi() { delay(10); // We start by connecting to a WiFi network Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } randomSeed(micros()); Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } void reconnect() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // Create a random client ID String clientId = "ESP32Client-"; clientId += String(random(0xffff), HEX); // Attempt to connect if (client.connect(clientId.c_str(),MQTT_USER,MQTT_PASSWORD)) { Serial.println("connected"); //Once connected, publish an announcement... client.publish("/icircuit/presence/ESP32/", "hello world"); // ... and resubscribe client.subscribe(MQTT_SERIAL_RECEIVER_CH_1); client.subscribe(MQTT_SERIAL_RECEIVER_CH_2); client.subscribe(MQTT_SERIAL_RECEIVER_CH_3); client.subscribe(MQTT_SERIAL_RECEIVER_CH_4); client.subscribe(MQTT_SERIAL_RECEIVER_CH_5); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Wait 5 seconds before retrying delay(5000); } } } void callback(char* topic, byte *payload, unsigned int length) { char in[length+1]={0}; memcpy(in,payload,length); // функция копирует массв в другой - in. Это нужно, чтобы потом проще передавать этот массив, без указания его длины length // if (!Charge_Level)//выполняем, если только батарея не разряжена // { Motors (topic, in); // } // else // { // Serial.println("Батарея разряжена-замените батарею!"); // } // Serial.println("-------new message from broker-----"); // Serial.print("channel:"); // Serial.println(topic); // Serial.print("data:"); // Serial.write(payload, length); // Serial.println(); } //---------- Скорее всего - лишняя функция, ее можно вырубить -------------- void publishSerialData(char *serialData){ if (!client.connected()) { reconnect(); } client.publish(MQTT_SERIAL_PUBLISH_CH, serialData); } //-------------------------------------------------------------------- //-------------------------------------------------------------------- void setup() { Serial.begin(115200); Serial.setTimeout(500);// Set time out for setup_wifi(); client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); reconnect(); // переключаем контакты моторов в режим «OUTPUT»: pinMode(motor1Pin1, OUTPUT); pinMode(motor1Pin2, OUTPUT); pinMode(motor2Pin1, OUTPUT); pinMode(motor2Pin2, OUTPUT); // подключаем контакты оптических датчиков к пину, в режим «INPUT»: pinMode (SensorPin1, INPUT); pinMode (SensorPin2, INPUT); // подключаем реакцию на прерывания - к пинам датчиков: attachInterrupt(digitalPinToInterrupt(SensorPin1), sensor_test, HIGH); attachInterrupt(digitalPinToInterrupt(SensorPin2), sensor_test2, HIGH); // задаем настройки ШИМ-канала каждого из 2 двигателей: ledcSetup(pwmChannel, freq, resolution); // первый двигатель ledcSetup(pwmChannel2, freq2, resolution2); // второй двигатель // подключаем ШИМ-канал, к контактам для управления скоростью вращения каждого из 2 моторов: ledcAttachPin(motor1Pin1, pwmChannel); // первый двигатель ledcAttachPin(motor2Pin1, pwmChannel2); // второй двигатель // подаем на контакты ШИМ-сигнал с коэффициентом заполнения «0»: ledcWrite(pwmChannel, dutyCycle); ledcWrite(pwmChannel2, dutyCycle2); }//setup void loop(){ client.loop(); //-------- читаем уровень заряда батареи--------- // Charge_Level (); //----------------------------------------------- //--------- выводим скорости двигателей --------- // SpeedCheck3 (); // MotorTester (); //----------------------------------------------- //Motors_controller (); if (Serial.available() > 0) { char mun[501]; memset(mun,0, 501); Serial.readBytesUntil( '\n',mun,500); publishSerialData(mun); } }
А вот функция, для управления двигателями(ниже):
// Функция, управляющая двигателями void Motors (char* topic, char ch []) { String s = topic; char temp; // выясняем, команда на вкл или выкл мотора if(ch[0]=='0') { // Serial.println("команда: выключить двигатель"); temp = '0'; // отключить мотор } else if (ch[0]=='1') { // Serial.println("команда: включить двигатель"); temp = '1'; // включить мотор } // volatile uint32_t stright_moving_time = 0; volatile uint32_t turning_time = 0; //сюда пишется время, как долго нажата клавиша поворота, во время езды вперед или назад int speed_temp_forward = 200; // переменная для ШИМ, при подруливании-во время движения вперед if ((s.indexOf("ctrl")>0)&&(temp=='1')) { CtrlState =true; Serial.println("Нажат Ctrl"); } else if ((s.indexOf("ctrl")>0)&&(temp=='0')) { CtrlState =false; Serial.println("Ctrl отпущен"); } else if ((s.indexOf("left")>0)&&(temp=='1')) //ПОВОРОТ ВЛЕВО { if (state.equals ("Stop")) //выполняем поворот, только если кнопка какого-либо движения отпущена. Это нужно для подруливания-иначе не получится { if (CtrlState) //если нажат Ctrl-поворачиваемся медленно { Serial.println("LeftSlow"); // "Влево медленно" state = "LeftSlow"; ledcWrite(pwmChannel, 20); ledcWrite(pwmChannel2, 235); digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); } else { Serial.println("Left"); // "Влево" state = "Left"; ledcWrite(pwmChannel, 255); ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); } } else if (state.equals ("Forward")) // ПОДРУЛИВАНИЕ ВЛЕВО { Serial.println("Подруливание влево"); state = "Left_Correction"; //-------подруливание происходит торможением левого двигателя, а правый крутится как и должен ----------- ledcWrite(pwmChannel, 0); // ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); // digitalWrite(motor2Pin1, LOW); // digitalWrite(motor2Pin2, LOW); } } else if ((s.indexOf("right")>0)&&(temp=='1')) // ПОВОРОТ ВПРАВО { if (state.equals ("Stop")) //выполняем поворот, только если кнопка какого-либо движения отпущена. Это нужно для подруливания-иначе не получится { if (CtrlState) //если нажат Ctrl-поворачиваемся медленно { Serial.println("RightSlow"); // "Вправо медленно" state = "RightSlow"; ledcWrite(pwmChannel, 235); ledcWrite(pwmChannel2, 20); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); } else { Serial.println("Right"); // "Вправо" state = "Right"; ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 255); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); } } else if (state.equals ("Forward")) // ПОДРУЛИВАНИЕ ВПРАВО { Serial.println("Подруливание вправо"); state = "Right_Correction"; //-------подруливание происходит торможением правого двигателя, а левый крутится как и должен ----------- // ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 0); // digitalWrite(motor1Pin1, LOW); // digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); } } else if ((s.indexOf("forward")>0)&&(temp=='1')) // ДВИЖЕНИЕ ВПЕРЕД { if (state.equals ("Stop")) //выполняем поворот, только если кнопка какого-либо движения отпущена. Это нужно для подруливания-иначе не получится { Serial.println("Forward"); // "Вперед" state = "Forward"; ledcWrite(pwmChannel, 200); //0 - максимум оборотов ledcWrite(pwmChannel2, 200); //0 - максимум оборотов digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); } } // //// //подруливание осуществляется так: начинаем замедлять один из двигателей //// //// while ( !(temp=='0') ) //// { //// turning_time = millis (); // время, когда началось отклонение. Это нужно, для того, чтобы построить "дугу" поворота влево //// //// while (millis() - turning_time<=50) //// { //// ledcWrite(pwmChannel, speed_temp_forward); //0 - максимум оборотов //// } //// //// speed_temp_forward = speed_temp_forward+5; //// //// if (speed_temp_forward >=255) //не даем установить больше максимума- который поддерживает ШИМ. Принудительно ограничиваем //// //// { //// speed_temp_forward = 255; //// } // //// } // // } // else if ((s.length()==0)&&(state.equals("Left_Correction"))) // ПОДРУЛИВАНИЕ ВЛЕВО-ЕСЛИ ОНО ТОЛЬКО НАЧАЛОСЬ // { // Serial.println("Подруливание влево"); // state = "Left_Correction"; //// uint32_t CorrectionStartTime = millis(); // время, когда началось отклонение. Это нужно, для того, чтобы построить "дугу" поворота влево // // //пробуем медленный поворот влево // ledcWrite(pwmChannel, 55); // ledcWrite(pwmChannel2, 195); // digitalWrite(motor1Pin1, HIGH); // digitalWrite(motor1Pin2, LOW); // digitalWrite(motor2Pin1, LOW); // digitalWrite(motor2Pin2, HIGH); // // } else if ((s.indexOf("reverse")>0)&&(temp=='1')) // ДВИЖЕНИЕ НАЗАД { if (state.equals ("Stop")) //выполняем поворот, только если кнопка какого-либо движения отпущена. Это нужно для подруливания-иначе не получится { Serial.println("Reverse"); // "Назад" state = "Reverse"; ledcWrite(pwmChannel, 55); //255 - максимум оборотов (Sensor 2) ledcWrite(pwmChannel2, 55); //255 - максимум оборотов (Sensor 1) digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); } } else if ( (state.equals ("Forward"))&&((s.indexOf("forward")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ДВИЖЕНИИ ВПЕРЕД { Serial.println("Forward stop"); // "Стоп" state = "Stop"; ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); } else if ( (state.equals ("Reverse"))&&((s.indexOf("reverse")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ДВИЖЕНИИ НАЗАД { Serial.println("Reverse Stop"); // "Стоп" state = "Stop"; ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); } else if ( ( (state.equals ("LeftSlow"))||(state.equals ("Left")) ) &&((s.indexOf("left")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ПОВОРОТЕ ВЛЕВО { Serial.println("Left Stop"); // "Стоп" state = "Stop"; ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); } else if ( ( (state.equals ("RightSlow"))||(state.equals ("Right")) ) &&((s.indexOf("right")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ПОВОРОТЕ ВПРАВО { Serial.println("Right Stop"); // "Стоп" state = "Stop"; ledcWrite(pwmChannel, 0); ledcWrite(pwmChannel2, 0); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); } else if (( state.equals ("Left_Correction")) &&((s.indexOf("left")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ПОВОРОТЕ ВЛЕВО { Serial.println("Left Correction Stop"); // "Стоп" state = "Forward"; ledcWrite(pwmChannel, 200); //0 - максимум оборотов ledcWrite(pwmChannel2, 200); //0 - максимум оборотов digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); } else if (( state.equals ("Right_Correction")) &&((s.indexOf("right")>0)&&(temp=='0')) )// ОСТАНОВКА ПРИ ПОВОРОТЕ ВЛЕВО { Serial.println("Right Correction Stop"); // "Стоп" state = "Forward"; ledcWrite(pwmChannel, 200); //0 - максимум оборотов ledcWrite(pwmChannel2, 200); //0 - максимум оборотов digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); } // else if ( ((s.indexOf("right")>0)&&(temp=='0'))||((s.indexOf("left")>0)&&(temp=='0'))||((s.indexOf("forward")>0)&&(temp=='0'))||((s.indexOf("reverse")>0)&&(temp=='0')) )// ОСТАНОВКА // { // Serial.println("Stop"); // "Стоп" // state = "Stop"; // // ledcWrite(pwmChannel, 0); // ledcWrite(pwmChannel2, 0); // digitalWrite(motor1Pin1, LOW); // digitalWrite(motor1Pin2, LOW); // digitalWrite(motor2Pin1, LOW); // digitalWrite(motor2Pin2, LOW); // } }
видимо надо вводить режим защиты от пропадания канала связи
Да, хорошее замечание. Эту сторону дела я как то упустил!
Пришла такая мысль:принудительно устанавливать соединие "снизу-вверх"- то есть от esp32-к роутеру. Каким образом: в коде выше -есть кусок, который постит некое сообщение -в некий топик. Сделать так: чтобы каждые 0,3 секунды или чаще- esp32 постила сообщение в топик. Если соединение отсутствует-устанавливала его принудительно.
Пришла такая мысль:принудительно устанавливать соединие "снизу-вверх"- то есть от esp32-к роутеру. Каким образом: в коде выше -есть кусок, который постит некое сообщение -в некий топик. Сделать так: чтобы каждые 0,3 секунды или чаще- esp32 постила сообщение в топик. Если соединение отсутствует-устанавливала его принудительно.
Стесняюсь спросить... А было как ?
А было как в коде выше ;-)
Установлено соединение с WIFI, MQTT-а дальше..дальше хрен бы с ними :-)))
Но, как показывает практика, видимо этого недостаточно. Соединение то ли переходит в режим пониженного энергопотребления или не знаю чего там. И свзяь становится какой то нестабильной. Хотя, может проблема и в питании системы(его недостатке).
Что конкретно вы имеете в виду говоря - "Если соединение отсутствует-устанавливала его принудительно.". А если сервер выключен, если оборван канал связи ? У вас всегда устанавливается связь снизу-вверх. Низ - клиент, верх - MQTT сервер. Причем сервер не может вам что то отправить. Вы можете только получить ответ сервера. Сервер - доска на которой можно опубликовать или прочитать. Именно поэтому эту технологию не используют для управления чем либо. Как правило ее используют там где быстрая реакция клиента не нужна - датчики, системы мониторинга и пр.
Именно поэтому эту технологию не используют для управления чем либо. Как правило ее используют там где быстрая реакция клиента не нужна - датчики, системы мониторинга и пр.
Ага, читал про это. Но в реальности, после множества тестов- могу сказать совершенно ответственно: скорость передачи данных при такой технологии -с лихвой перекрывает все потребности по управлению машинкой. В миллисекундах время прохождения данных не скажу, но субъективно, выглядит как "мгновенно".
Победить отвал сети(а возможно и просто подвисание системы из за питания от того же источника питания, что и двигатели) и всё - проблем нет.
Отвал сети у вас скорее всего из-за неправильно установленного периода жизни клиента, на самом клиенте или на сервере. Разбирайтесь.
пока временно удалось справиться так: при потере соединения с wifi: переподключиться. При потере соединения с mqtt- то же самое.
Правда это мертвому припарки пока что :-B
Ищу причину отвала. Пробовал гарантированно мощный источник питания-не в этом дело оказалось. Ищу пока что...
Всё -проблема с отвалом сети решена! Проблема оказалась вовсе не там, где ожидалось. В чем оказалась проблема(если вкратце): приход большого количества сообщений по MQTT внутри каждого из 5 каналов - приводил к окончательному стопору всей системы и вылету.
Как это происходило: веб интерфейс на сайте был настроен так, что пока нажата какая либо из управляющих кнопок на клавиатуре:
↑↓←→ Ctrl - шлется непрерывным потоком сообщение в определенный топик MQTT. Несмотря на то, что сообщение короткое (0 или 1), - от прихода сотен сообщений по 5 каналам и их разбора - система просто умирала :-). Не исключаю, что я рукожоп и мой код кривой, а "риал труъ прогер" бы сделал как надо :-) Вот и нашел зато физические пределы esp32.
Когда переделали веб интерфейс управления на сайте, чтобы при нажатии на любую управляющую кнопку- слалось только 1 сообщение в соответствующий топик и при отжатии кнопки-тоже только 1 сообщение туда же, - система стала работать супер стабильно. Просто чётко как часы! :-)