Как правильно работать с GSM модулем (да и любым устройством по serial) без delay?
- Войдите на сайт для отправки комментариев
Считаю, что код в стиле:
gsmSerial.println("ATH"); delay(5000); gsmSerial.println("ATD>1;"); delay(30000); gsmSerial.println("ATD>2;");
не совсем правильный, хоть и прост, и правильнее было бы работать без delay, но это достаточно сложно, так как нужно сначала запросить что-то, потом ждать ответ и правильно его соотнести, так как может быть ответ сначала на другой запрос или вообще например сообщение о низком заряде аккумулятора, а ждешь статус модуля допустим, поэтому включенное ЭХО может и пригодиться (если я правильно понял его работу).
В общем набросал звонок на первый номер, и уже каша в коде, а это еще нет проверки ответа, занято, линия недоступна или сброшен вызов, или все ок, и т.п., кто как считает, как правильнее позвонить допустим, но чтобы все учесть, если вдруг сейчас идет звонок или модуль вообще завис и не отвечает, значит его перезагрузить и т.п.
Тут в коде идет проверка статуса модуля и если он не готов к звонку, то положить трубку если идет разговор или запросить попозже, если модуль чем-то занят, если нет ответа более 5 сек., то еще запросить и так 5 раз, если ничего непомогло перезагрузка модуля, и всё по-новой через 20 сек. Если же модуль готов, то позвонить на первый номер, дальше нет кода, но будет уже проверка ответа на запрос звонка (занят, нет сети, нет ответа и т.д.), если что звонок на второй номер, или также перезагрузка и т.д. и т.п.
void loop() { static unsigned long currentMillis = millis(); static boolean alarm = false; static byte alarmMode = 0; static unsigned long alarmPreviousMillis = 0; if(какое-то условие) alarm = true; if(gsmSerial.available()) { char inChar = (char)gsmSerial.read(); if(inChar == '\r') { if(gsmString.startsWith("+CPAS:")) // текущее состояние модуля { if(alarmMode == 1 || alarmMode == 3 || alarmMode == 5 || alarmMode == 7 || alarmMode == 9) { String pasString = gsmString.substring(7); // 0 – готовность к работе, 2 - ответ и выполнение команд не гарантируется, 3 - идет входящий звонок, 4 установлено голосовое соединение if(pasString == "0") { gsmSerial.println("ATD>1;"); // позвонить на номер в первой ячейке СИМ alarmMode = 21; alarmPreviousMillis = currentMillis; } else if(pasString == "3" || pasString == "4") { gsmSerial.println("ATH"); // завершить все соединения alarmMode = 11; alarmPreviousMillis = currentMillis; } else //2 { if(alarmMode < 9) { alarmMode++; alarmPreviousMillis = currentMillis; } else { pinMode(rstGSMPin, OUTPUT); // сброс модуля delay(500); digitalWrite(rstGSMPin, INPUT); alarmMode = 10; alarmPreviousMillis = currentMillis; } } } } else if(gsmString == "OK") { if(alarmMode == 11) { gsmSerial.println("ATD>1;"); // позвонить на номер в первой ячейке СИМ alarmMode = 21; alarmPreviousMillis = currentMillis; } } gsmString = ""; } else if (inChar != '\n') { gsmString += inChar; } } if(alarm) { if(alarmMode == 0) { gsmSerial.println("AT+CPAS"); // запросить текущее состояние модуля alarmMode = 1; alarmPreviousMillis = currentMillis; } else if(alarmMode >= 1 && alarmMode <= 8) { if(currentMillis - alarmPreviousMillis >= 5000) { gsmSerial.println("AT+CPAS"); // запросить текущее состояние модуля alarmMode += alarmMode%2 + 1; alarmPreviousMillis = currentMillis; } } else if(alarmMode == 9 || alarmMode == 11) { if(alarmMode == 9 && currentMillis - alarmPreviousMillis >= 5000 || alarmMode == 11 && currentMillis - alarmPreviousMillis > 20000) { pinMode(rstGSMPin, OUTPUT); // сброс модуля delay(500); digitalWrite(rstGSMPin, INPUT); alarmMode = 10; alarmPreviousMillis = currentMillis; } } else if(alarmMode == 10) { if(currentMillis - alarmPreviousMillis >= 20000) { alarmMode = 0; alarmPreviousMillis = currentMillis; } } } }
Вижу чтение одного байта в 14 строке и много записи.
Что такое gsmString? Вижу много работы с этим объектом на чтение, но ничиге в него не пишется.
Вот как хреновый руководитель: орать умеет, а слушать не хочет.
Кричишь в Serial команду и в цикле ждешь ответ, считая период ожидания при этом. Если нарвался на таймаут ответа, то рапортуешь об ошибке.
Вот поэтому и спросил, потому что чувствую, что есть более правильный путь, только какой? Или все так, только граф нарисовать и по нему продолжать писать?
Что такое gsmString? Вижу много работы с этим объектом на чтение, но ничиге в него не пишется.
Туда складываются байты, а когда это становится полной строкой, то разбирается, что за сообщение пришло.
Граф - это половина успеха, он легко перекладывается в код.
Andy, Допустим, то что написано в первом посте написано по графу, то алгоритм нормальный или Вы что-нибудь переделали? Подскажите, пожалуйста, просто хочется сразу правильно писать.
То что написано, к графу состояний не имеет отношения, это не более, чем реализация алгоритма из кубиков и ромбиков. Надо описать именно состояния устройства.
готовность к работе, идет входящий вызов, установлено голосовое соединение - это все состояния.
Если Вы имеете ввиду переменную, в которой находится значение в каком состоянии сейчас устройство до момента когда захочется позвонить, то если бы она была, допустим был входящий звонок и он отвечен, то все равно в этой переменной будет то что сейчас звонок, а завершен он или нет, неизвестно, но можно конечно следить за этим наверное, еще не проверял, но даже если можно, надежнее проверить все равно, как тут 5 раз и делается, точнее 1 раз точно, а если что-то пошло не так, то еще попытки. Как же по-вашему будет выглядеть алгоритм на словах или в коде, хотя бы первые итерации так сказать, а то непонятно что не так и что бы Вы поменяли...
Прмерно так:
В любом случае надо сначала описать все состояния устройства, условия переходов из состояния в состояние и действия выполняемые при этом.
Так у меня тоже самое написано, только на if, потому что в данном случае гибче, типа "if(alarmMode >= 1 && alarmMode <= 8)", чем case 1: case 2: ... case 8: и вообще можно любые условия ставить.
Удачи.
Ну чтож сделал на if, но понял что с цифрами не очень удобно работать, и как раз для этого есть enum, назвал все цифры словами так сказать, стало удобнее, и переделал в итоге на switch, так как все равно других условий нет, кроме alarmMode, а также там и компилятор проверяет если что не так, например не все переменные из enum указаны.
Очень неправильный подход. Первое: сразу переделывайте на serialEvent - это сильно лучше и грамотнее. Второе - конечный автомат вас спасёт. Главное условие - никаких блокировок, это, можно сказать, залог успеха и защиты от потери данных вследствие переполнения буфера RX.
В общем случае алгоритм прост: в serialEventN (смотря какой хардварный Serial вы пользуете) вычитываете данные построчно. Как придёт строка - только тогда обрабатываете её. Обработали, поняли, что нужно сделать - сменили состояние автомата, чтобы при следующем заходе в loop выполнить требуемое действие. Псевдокод:
Сходу всего не напишешь, поэтому советую почитать информацию по конечным автоматам ;)
Я не на меге делаю, и аппаратный Serial оставил для логирования, хотя и надо только пока тестируется, но и с программным все в принципе работает. Да и прошивать проще, можно не отключать от модуля.
Возможно Вы думаете, что serialEvent использует прерывания и поэтому рекомендуете (извините если не так), но на самом деле это просто функция вызывается каждый раз в loop, то есть, что сам напишешь, что ее вызывать, все равно, а то может и больше затрат, так как еще функция вызывается, а не сразу код. Но в любом случае в SoftwareSerial все равно нет этой функции...
Ну а по коду, я примерно так и делаю, только без вызова фукнции, а прямо в коде... И переделал с вложенных состояний в ответах, наоборот сначала проверка состояний, а потом внутри уже от ответа "плясать", посчитал так удобней. Опытным путем экономлю память, правда пока хватает, просто заранее на всякий случай, разница и в вызове фукнции, если она один раз вызывается и передача переменной по ссылке против глобальной, все влияет...
> Возможно Вы думаете, что serialEvent использует прерывания и поэтому рекомендуете (извините если не так), но на самом деле это просто функция вызывается каждый раз в loop, то есть, что сам напишешь
Читать до полного просветления.
https://www.arduino.cc/en/Reference/SerialEvent
https://www.arduino.cc/en/Tutorial/SerialEvent
Возможно Вы думаете, что serialEvent использует прерывания и поэтому рекомендуете (извините если не так), но на самом деле это просто функция вызывается каждый раз в loop, то есть, что сам напишешь, что ее вызывать, все равно, а то может и больше затрат, так как еще функция вызывается, а не сразу код.
Я в курсе, спасибо. Но использовать serialEvent всё равно грамотнее, чем просто вычитывать самому по while и ждать, не находите? Накладные расходы грамотный компилятор, к слову, сведёт к минимуму, это раз. Два - функция сама вызывается только тогда, когда в порту есть данные - уже удобно, не надо специально ничего предпринимать. Три - использование serialEvent позволяет избавиться от головняков вида "блин, и вот тут надо проверять, есть ли данные из порта!" Ну и, наконец - использование такого подхода позволит легко и быстро, при необходимости, переписать кусок кода на прерывания, никак не затрагивая ваш конечный автомат.
Скажем, опять же, из опыта - тупой код в лоб с вычитыванием данных из одного порта до посинения, пока в другой приходят данные - приводит к потере данных из второго порта вследствие переполнения буфера. В этом контексте serialEvent можно рассматривать как таймслоты, которые делят между собой порты. Мне каицца, что именно для этого всё и было задумано ;)
Radjah, а что читать, то,
вот то что я и говорю:
А вот код из исходников Arduino:
DIYMan, спасибо, вероятно, в моем коде лучше if(gsmSerial.available()), заменить на while, чтобы считывалось за раз если есть и знать, что бесконечно данных не будет. Но даже с serialEvent с while внутри, как Вы и написали, данные будут считываться пока не кончатся, даже если в другие сериалы придет что-то, и также данные потеряются. Так как функции все вызываются по порядку, пока не отработает предыдущая.
Но даже с serialEvent с while внутри, как Вы и написали, данные будут считываться пока не кончатся, даже если в другие сериалы придет что-то, и также данные потеряются. Так как функции все вызываются по порядку, пока не отработает предыдущая.
Всё правильно вы пишете, но не принимаете во внимание один нюанс, при работе с несколькими портами. Допустим, в первый порт мы послали команду и нам надо дождаться ответа. Псевдокод:
То такой вот подход с while наглухо блокирует работу с остальными портами, пока на нужном порту вы не вычитаете ответ. Кстати - довольно распространённый подход среди негодных интернет-примеров ;)
В результате - потеря данных на других портах. Если же юзать банальный serialEvent, то всё простейшим образом расщепляется на таймслоты, т.е. - послали команду, а в нужном serialEvent вычитываем информацию, и когда получаем ответ - делаем что-то. При этом имеем возможность автоматически работать с другими портами, конечно, при условии грамотной организации кода, как вы правильно заметили - если висеть почём зря и ждать, когда порт разродится данными - не поможет и serialEvent.
Просто я считаю, что раз есть достаточно удобный инструмент, уже дающий необходимую обвязку - то грех им не пользоваться. Хотя - не настаиваю ни капли, каждый пляшет как умеет.
В результате - потеря данных на других портах. Если же юзать банальный serialEvent, то всё простейшим образом расщепляется на таймслоты, т.е. - послали команду, а в нужном serialEvent вычитываем информацию, и когда получаем ответ - делаем что-то. При этом имеем возможность автоматически работать с другими портами, конечно, при условии грамотной организации кода, как вы правильно заметили - если висеть почём зря и ждать, когда порт разродится данными - не поможет и serialEvent.
Просто я считаю, что раз есть достаточно удобный инструмент, уже дающий необходимую обвязку - то грех им не пользоваться. Хотя - не настаиваю ни капли, каждый пляшет как умеет.
пример проблемного кода увидел, суть понял.
а можно дать пример(лучше два :) ) как нужно делать в таком случае, наглядный пример он полезнее, спасибо.
пример проблемного кода увидел, суть понял.
а можно дать пример(лучше два :) ) как нужно делать в таком случае, наглядный пример он полезнее, спасибо.
Пример с serialEvent:
Впрочем, serialEvent - это не прерывания, конечно. Для действительно неблокирующей другие порты вычитки надо либо дописывать HardwareSerial, либо - вовсе отказываться от него и обрабатывать прерывания ручками.
Обратите внимание на использование функции yield - это хороший тон для обеспечения кооперативной работы. Если вы пишете библиотеку или класс, который внутри себя может выполнять длительную работу и при этом хотите, чтобы другие части кода тоже могли что-то делать, то в нужных местах ставите вызов функции yield. По сути - это пустой вызов, но в скетче вы можете определить эту функцию, написав
и прописав туда нужный код. Естественно, что в приведённом примере внутри функции yield не стоит вызывать serialEvent, иначе - стек попортите рекурсией.
Короче - подходов есть много разных, и в документации они прописаны. Жаль, что никто её не читает :)
Доброго дня.
Ветка конечно заброшенная, но ответ на неё мне кажется актуальным и для меня не найденным, все ответы в данной теме были плавно переключены не на избавление от delay, а на парсинг из serial, а судя по названию темы и вопросу создателя в первом сообщении, как правильно опрашивать gsm модем без использования delay, т.к. при опросе нужно делать задержки и они могут быть разными т.к. например есть функция запроса баланса
в ней после запроса стоит delay на ожидание прихода данных от оператора, и в этот момент МК зависает, вместо того что-бы делать полезную работу.
Так вот вопрос Как оптимизировать функцию что б избавиться от delay?
PS. Есть предположение что её можно разбить путём выноса
в основной блок опроса Serial крутящийся в loop.
Подтвердите моё предположение или есть более грамотное решение?
PS2 Ещё есть ф-ция отправки сообщения
в ней тоже имеется 2 delay (пусть крохотные, но есть) и если их убрать то иногда некорректно отрабатывает (правда было проверенно на SoftwareSerial).
все ответы в данной теме были плавно переключены не на избавление от delay, а на парсинг из serial
да, именно так.
необходимо полностью переосмыслить работу с потоком данных, обрабатывать не всю принятую цепочку данных (да она тупо и не влезет в память ардуины), а побайтно или по логическим строкам/словам.
Так вот вопрос Как оптимизировать функцию что б избавиться от delay?
Никак, полностью переписать (см.выше).
вот вам пример перекодировки SMS , обратите внимание идет последовательная обработка входящих данных
а вот например отправка СМС, аналогично побайтно берется отправляемая строка и в каждом loop цикле (образно) отправляется уже байтв кодированном виде в модем:
Спасибо, смысл понятен, буду устраивать себе мозговой штурм.
PS У меня в буфер Serial всё и не помещалось, ну я и пошёл неправильным путём, увеличил размер буфера!
ну я и пошёл неправильным путём, увеличил размер буфера!
ну...для начала это правильный путь, потом уже с опытом все короче и короче делается.
в идеале размер буфера не должен быть больше одной принимаемой команды.
и да, старайтесь не использовать тип String, привыкайте к массиву char.
вот немного кривой но рабочий пример, буфер 24 байта
http://arduino.ru/forum/apparatnye-voprosy/vse-o-sim800l-i-vse-chto-s-ni...
Ветка конечно заброшенная, но ответ на неё мне кажется актуальным и для меня не найденным, все ответы в данной теме были плавно переключены не на избавление от delay, а на парсинг из serial, а судя по названию темы и вопросу создателя в первом сообщении, как правильно опрашивать gsm модем без использования delay,
Именно поэтому новички, зачастую, не могут правильно сформулировать вопрос, - их знаний даже на половину ответа не хватает.
И именно поэтому более опытные участники форума "плано переключают" обсуждение темы с неправильно заданного вопроса на тот, который следовало бы задать как правильный.
Другими словами, правильный ответ, как правило, не является буквальным ответом на заданный (непрнавильный) вопрос.