И почитать раздел "типы данных". Ознакомится со всеми ими. Это как ключи/отвертки у мастера. Он должен знать "какие у него есть" и старатся подбирать наиболее подходящий по размеру в каждой ситуации.
Ознакомитесь - попробуйте заменить int на что-то более экономное к памяти. То есть - в данном случае это не критично, но привыкать использовать "минимально подходящий" тип - нужно. У ардуины не так много ресурсов, что-бы разбрасываться ими не думая :)
Не соглашайтесь. Только switch для bool переменной - это дуристика. Сам же //Hm.. написали.
У вас вышел более громоздкий, трудней читаемый, трудней модифицируемый код (а ну как нужно будет усложнить условие?)
Если очень хочется "повыеживатся" (Life23 - не читай, так делать - плохой стиль), то можно было вообще упаковатся в одну строку
if(prevBtn!=currBtn)sendTextMessage(!currBtn);
Но за такой код - нужно по рукам бить. Потому что тот кто будет читать его после тебя (или ты сам через годик) - мозги расплавит, что-бы понять "что тут происходит".
Так что те if-ы, в данной ситуации - самое нормальное решение.
Это был просто пример, человек, насколь мне видится, с удовольствием учится, вот поэтому я и предлагал его. Но чуствуй что "у семи нянек дитя без глазу", я тут только мешаюсь да и Ваши знания в ардуино мои превосходят, так что встревать больше не буду.
ПС: на вкус и цвет все фломастеры разные, в данном конкретном примере я бы тоже не свитч использовал а иф вложенный, но тут как говорится кому что больше нравится :)
понял. "int btnPin = 2" удалил и заменил на #define btnPin 2
Ага. Только лучше уж однотипно это делать c RED_LED_PIN. Скажем BTN_PIN назвать.
Тогда, читая код, вы сразу будете понимать "если что-то названо целиком большими буквами" - это #define. Изменять в коде его значение - мы не можем. Если же оно "с маленькой буквы", то это переменная.
Вообщем если придерживатся такой стилистики, то не глядя в объявления всегда можно догадатся что bntPin - это переменная, а BTN_PIN - это дефан :)
А сама идея - хорошая. analogRead(2) - фиг его пойми что значит, а когда читаешь analogRead(btnPin) - сразу понимаеш что это чтение состояние кнопки :)
Вот если постороннему человеку (или вам через пол года) показать строку
sendMessage(0)
или
sendMessage(MSG_IDX_SUCСESS);
В каком варианте человек быстрее догадается, что эта строчка делает?
PS:
MSG - message - сообщение
IDX - index - индекс
SUCCESS - удачно, подтверждение...
что называеться "слИпил" коды.. Страшно вылаживать,
Значит - рано :) Не дополировали еще "детали" что-бы они легко и без напряга друг к другу подошли :)
Может даже работать будет (не вникал), но... вникать-то тяжело. "Не причесан" еще код. Когда "дочешем его" - тогда и "как слепить" - будет самоочевидно :)
Да и более "устойчивым к разным ситуациям" он, я думаю, станет :)
я правильно Вас понял: надо ввести еще значения функции, да бы подтвердить какое именно сообщение отправленно и отправленно ли оно?
Тут уже я вас не понял :(
Нет. Я иммел ввиду, что "любые цифры в коде" - это зло. "Голая цифра" - ничего не говорит читающему (в большинстве случаев, в любом правиле есть исключения). Даже термин есть устоявшийся "магические цифры". Это какие-то цифры, забитые прямо в код. С этими цифрами код работает. Но почему эти цифры именно такие - понятно только автору. Поэтому они "магические". Наличие таких цифр - признак кода который тяжело сопровождать.
Поэтому, где возможно - стараемся заменять их константами/дефайнами
С точки зрения компилятора - это вообще идентичные скетчи. С точки зрения читающего - "О... сразу понятно что послали сообщение о том что кнопку нажали/отпустили".
С нашей точки зрения - чуть-чуть больше кода набрать нужно :) Но IMHO оно того стоит. Не велика расплата за читабельный код (потом не раз себя похвалим).
Ну сами строки (текст sms) - можно было и оставить "Knopka On", "Knopka Off". Это уже "то что видит человек", поэтому главное - чтобы пользователю было понятно. А обычному человеку подчеркивание между словами - немного не привычно. Не програмеру - привычней видеть пробелы :) (вот... уже и у вас мозги начинаю на програмерски бекрень наклонятся :)
А теперь. Вы наверное решите что я издеваюсь :) - выкидываем эти MSG_IDX_BUTTON_PRESSED и MSG_IDX_BUTTON_RELEASED ;)
Просто нужно было показать, что "магические цифры" - зло. И как с ними бороться. Использование #define для этого - обычная практика.
Но... если отказатся от дополнительного массива myStrings[] (тоже, кстати, слишком общное название. ничего не говорящие, что-то типа smsMessages[] - было бы удачней), так вот... если мы вообще от него откажемся, то у нас вообще отпадет надобность в цифрах. Исчезнет массив - исчезнут индексы, не будет индексов - не нужны цифры, не нужны цифры - нечего заменить на #define-ны :)
Вы говорили что "параметры функции" уже не так страшно :)
Давайте попробуем переделать функци sendTextMessage() что-бы она принимала не индекс, а строку. Что-бы параметр был не цифровой, а текстовый.
Что-бы пользоватся ей можно было так:
sendMessage("Hello from Arduino");
Подсказка: нужно поменять тип параметра функции с int на.... что-то очень похожие на то как вы объявляли массив myStrings[]. Только там вы объявили "массив строк", а тут вам нужно, даже проще "объявить строку". Параметр типа строка, а не int как сейчас
p.s.: к стати, хоть легче стало читать код "обычному человеку", а размер скетча в двоичном коде от предыдущего вырос почти в два раза...
Ну, во первых тут уже вопрос "приоритетов". Что лучше купить камень более жирный (который стоит на $0.5 дороже, а иногда даже дешевле) или потратить неделю на ковыряния "в кошмаре нечитабельном".
Во вторых при потребности, точечно оптимизировать красивый код гораздо легче, чем код в котором изначально "эфективность" ставилась во главу угла. На маленьком скетче "байты выжимать ценой читабельности" - еще получится. А вот на большом - все равно выйдет "монстр прожорливый". Просто потому что вы потеряете над ним контроль (начнете больше ошибок делать, выделять память "на всякий случай", потому что не возможно понять "можно повторно использовать уже существующие переменные или нет" и т.д. и т.п.
В третьих. Код у вас вырос не потому что вы добавили ему читабельности. А потому что.... тип параметра не удачно выбрали :)
Я же вам писал подсказку "тип параметра должен бы очень похожим на то как вы объявляли myStrings[]". Был там тип String? Небыло. А тут - появился :) Вот он вам и насрал в кашу.
Выше по ветке я советовал вам прочитать раздел доки "Типы данных" целиком (если читали - еще раз. я сам регулярно почитываю ;) Там вы увидите что у нас есть два вида строк.
Вы воспользовались String. Это чисто "Ардуина фишка". В стардатных сях таких строк нет. Не хочу глубоко вдаватся, тема холиварная, но лично мое мнение что в микроконтроллерах String - это зло. На одну бяку (размер) - вы уже наступили. А есть еще :)
А нужно было воспользоватся стандартными c-шными строками. string. Поменяйте объявление параметра функции на "char* sms" или "char sms[]" и посмотрите на размер скетча. Будете приятно удивлены :)
Вообщем мой вам совет: не ведитесь на простоту использования String. Осваивайте обычные сишный строки. Никуда вы от них не денетесь. Все равно везде где что-то сложней 10-ти строк - будете на них натыкатся.
Тема сишных строк внешне простая, но ... имеет подводные камни. В рамках форума - с ними не просто разобратся досконально. Так что советую взять учебничек (а лучше несколько) по C/C++ и покопатся в этих разделах (там еще на указатели потребуется помедитировать). Вообщем "строки/указатели" - потребуют какоего времени что-бы стать с ними "на Ты". Но это будет полезно потраченное время. Вообщем "задание на каникулы" вам:)
хм.. действительно код уменьшился в размере с char... :)
я и начинал как раз с char. но не хватило ума потом как правильно он объявляеться в "GPRS.println(sms);" (полез в дебри), а на сомом деле все просто. Да еще и лишнюю скобку втулил.. сбился с толку совсем.. За то теперь буду знать что такое String. ))))
раздел доки "Типы данных" я постоянно перечитываю.. но пока не попробуешь - бесполезно.. практика великое дело..
Явно не до конца поняли что такое define. (и правило именовайние - нарушили).
Что происходит с #defin-ом. В момент компиляции (важно, не в момент исполнения), даже "до компиляции", компилятор ищет по коду этот NumTel, если где-то находит - подставляет его туда и потом компилирует. Именно поэтому, с точки зрения компилятора
Оба варианта - скомпилируются, с точностью до байта в одно и тоже. Просто потому, что компилятор первый вариант, незаметно, преобразует во второй.
Так что "для человека" - это разный код. Для компилятора - тот же самый :)
Темперь смотрим, что у вас получилось. С вашим дефайном. КОгда он подставиться, то вы выйдет
GPRS.print(+79************)
Что не очень хорошо. Не дай бог в номере попадутся дефисы, круглые скобки.... получим кучу ругани что "цифра какая-то не непонятная". Поэтому мы должны делать print, не числа, а строки (как и было в изначальном). То есть - нам нужно, что-бы, после подстановки #define получилось что-то типа:
GPRS.print("+79********");
Тогда это строка, а в строке могут быть и дефисы и проч. Что хотим. Компилятор ругатся не будет.
Вообщем - потеряли вы кавычки. Ваш дефайн должен был выглядить так:
#define NUM_TEL "+79*******"
Но... не буду счас сильно вдаватся почему (память/указатели и т.п.), но в отличие от цифр, строки, особенно длинные лучше выносить в конфигурацию не с помощью #define, а с помощою обычных переменых. Ну просто такое правило :) Номера пинов, время задержки и проч. - объевляем #define-нами, строки - переменными.
Будет рабоать, но ... изначально же было сделано через экранивание двойной кавычки. Кто там помнит что такое 34? Поэтому одна строка
GPRS.print("AT+GMGS=\"");
вместо двух - будет лучше выглядить (косая черта означает "двойная ковычка это именно символ двойной кавычки, а не признак конца строки". Мы ее "заэкранировали" с помощью косой черты. Сказали компилятору что "следующий символ" - просто символ. Не пытайся найти в нем смысл :)
То же самое и с завершением.
Вообщем, вся эта лабуда у нас в итоге будет примерно такой
"въехал.." и за нотировал ваш пост себе в блокнотик )
но почему-то оба моих варианта компилировались без проблем.
в первом варианте, в #define я ж в скобки вписал номер телефона ))))) а вот без скобок компилятор меня послал )))
все это - зло! )) я уже понял )
а вот косая в (CMGS = \"+79************\"") меня и сбила с толку.. теперь буду знать о ней..
Во первых не "скобки", а "двойные кавычки" :) Во вторых, не послать, без кавычек, он мог только если номер действительно был только из цифр и знака плюс.
Ну во первых я бы порекомендовал, между строчками 33 и 34 вставить пустую строку. "Отбить их визуально". Что-бы четче было видно что 31,32,33 - формируют одну строку. Ну или вообще написать их "в одну строчку", как я сделал в #78 в конце. Но это даже "на замечание" не тянет. Скорее "дело вкуса".
OK. По "замечаниям". Давайте вы еще раз прочитаете Serial.print и Serial.println . Вникните в их различие. И что они делают. Особенно println. А потом посмотрите свой код... и подумаем, а зачем там в строках символ \r? Причем попадается он и в print и в println. Он там действительно нужен? Если нет - убрать, если нужен - единообразить все. А то "то так, то этак" :)
"Отбить их визуально".. АААААаа... я чуть было не грохнул ардуинку о пол.. )) но вовремя прочитал: Но это даже "на замечание" не тянет. Скорее "дело вкуса".)))))
Кстати - молодец что начал коментировать то что "может забыться" и не очевидно и с кода. Я про коменты к строкам (12), ( 14)
Кстати, вот так же, чуть-чуть "доофрмить" можно и шапку. Не перемешивать там, через строчку, переменные "для работы" и "то что конфигурирем". Плюс - комментом выделить, что-бы кому-то другому было легче разбиратся.
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете." (С) Стив Макконнелл
// *********** Настройки *************
// подключение
#define BTN_PIN 2
char NUM_TEL[]="+78*************"; // телефон куда будет отсылаться SMS
#define GPRS Serial
// ********* /Настройки ***************
boolean prevBtn = LOW;
OK. Считаем что с println и \r вы разобрались и сами поправите.
Едем дальше.
Что-то у нас с задержками после отсылки команды - полная чехарада. То 100, то 300, то 500. То исчезают, то появляются :) Да и каждый раз писать по две строчки - лениво же :)
Давайте тоже это дело единообразим. Сделаем вспомогательную функцию, которая будет отсылать команду gprs-су, и ждать пока он ее выполнит
И сразу, много где мы сможем вместо двух строчек написать одну :)
P.S. Пока необходимость выделять это в отделную функцию "притянута за уши". Две строки - можно было-бы и потерпеть, но.... чуть позже увидим что "выносить в отдельную функцию" - таки было нужно.
"Отбить их визуально".. АААААаа... я чуть было не грохнул ардуинку о пол.. ))
Ну вы уже поняли, что "код не пишется", код "выращивается" :) Кстати вот так "то тут поправили, то там", потом "чуток дописали нового функционала абы работал", потом опять "логику не меняем, но делаем что-бы код красивым был" (рефакторинг - название этого процесса). Так вот - это обычный процесс разработки. Не только у новичков.
А вообще - что же вы хотите? ;) За пару дней - тут у вас неплохой такой "интенсивчик" получился. Не каждый бы выдержал. В институте - этот объем легко бы на семестр растянулся. Конечно тут мы "сильно по верхам" пробежали, но круг новых понятий - не такой уж и маленький получился.
Немного оклемавшись от вскрытия моей машины какими-то уродами - иду дальше:
#define GPRS Serial
#define BTN_PIN 2
boolean prevBtn = LOW;
char NUM_TEL[]="+78*************";
void setup()
{
GPRS.begin(9600);
gprsCmdAndWait("AT+CMGF=1"); //выбор режима sms: 1-текстовый,0-цифровой)
gprsCmdAndWait("AT+IFC=1, 1");//data flow controll(1, Программный режим (XON/XOFF)).
gprsCmdAndWait("AT+CPBS=\"SM\"");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию.
gprsCmdAndWait("AT+CNMI=1,2,2,1,0"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю..
}
void loop()
{
boolean currBtn = digitalRead(BTN_PIN);
if (prevBtn != currBtn && currBtn == HIGH) {
sendTextMessage("knopka on");
}
if (prevBtn != currBtn && currBtn == LOW) {
sendTextMessage("knopka off");
}
prevBtn = currBtn;
}
void sendTextMessage(char* sms) {
GPRS.print("AT+CMGS=\"");
GPRS.print(NUM_TEL);
GPRS.println("\"");
delay(100);
GPRS.print(sms);
delay(100);
GPRS.println((char)26);
}
void gprsCmdAndWait(char* commandText){
GPRS.println(commandText);
delay(300);
}
склонный к насилию психопат - пускай пока подождет... не стал пока комментировать код и разделять, что бы текста пока было по меньше, пока "въеду" в сам смысл работы кода.
p.s.: по поводу "Программный режим (XON/XOFF)" читал много, что не стоит его использовать так как он не идеален и много с ним проблем. Глубоко не вникал - чисто для размышления..
А так: правда setup() стал симпатичней? ;) еще бы и в sendTextMessage тоже применить это gprsCmdAndWait() :)
Правда там так идеально он уже не применится и чуток мозг похмурить нужно будет что-бы его воткнуть (вообщем если не получится - покажу, только чуть позже). Но, кроме красоты, применение специальной функции для отсылки любой команды на gprs (и никогда не послать в ее обход) дает нам еще одно преимущество: если нам потребуется какая-то дополнительная логика при посылке команд (например "мигнуть диодом при этом", показать что ардуино что-то командует шилду) то это изменение можно внести в одно место. в функцию gprsCmdAndWait(), а не по всему коду лазить и искать где у нас команды посылаются.
Вообщем попробуйте, если сразу не получится - подождите до вечера :)
И еще... можно уже, потихоньку, начинать готовить это скетч к тому что он будет "интегрироватся с другим скетчем" (преставим что пока не знаем с каким именно).
Как вы уже поняли интеграция двух скетчей сводится, грубо говоря, к слиянию функций setup() и loop(). В идеальном случае какие setup() и loop() проще всего сливать? Конечно полностю пустые :) К сожалению - мы живем в реальном мире и пустые setup() и loop() - у нас небудут никогда. Но стремится к совершенству - нужно :) Пойдем на компромис. Постараемся их сделать если не пустыми, то хотя-бы как можно более тривиальными. Для этого всю "развесистую" логику мы вынесем в отдельные функции (причем еще и имена подберем так, что-бы сразу было понятно что делает эта "Развесистая логика") и будет из loop() и setup() вызывать эти функции.
Что-нибудь поняли из этого потока сознания? ;) Если нет, тогда более конкретно:
Все что сейчас в setup() - выносим в функцию с именем скажем initGPRS(); , вче что сейчас в loop() - выносим в функцию onChangedButtonSendSms();
Что-бы наши setup() и loop(), в итоге приняли вид:
Именно такого ответа я и боялся.. )) потому как сначала хотел применить данный финт с sendTextMessage ))) много полезло багов..
Попробую. До вечера еще время есть ) все таки когда сам до чего-то до шел - легче запомнить.. и понимаешь лучше - что к чему..
"мигнуть диодом при этом" - вполне полезная штука! визуальное оповещение работы устройства никогда не помешает.. не говоря уже о применение в других устройствах.
И еще чуть-чуть забегу вперед. Что-бы дальнеший ход мысли было легче понять.
Вопрос на засыпку: почему currBtn мы объявляли внутри функции, а prevBtn вверху скетча? Это просто "неакуратность" или "так и нужно"? Появляется ли из-за этого какая-то разница в поведении/свойствах этих двух переменных? Или можно объявлять где удобней автору?
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
#define GPRS Serial
#define BTN_PIN 2
boolean prevBtn = LOW;
boolean currBtn;
char NUM_TEL[]="+78*************";
void setup(){
initGPRS(); //вызов функции инициализации GPRS
}
void loop()
{
onChangedButtonSendSms(); // вызов Функции проверки изменения кнопки
}
void sendTextMessage(char* sms) { // Функция отправки SMS
gprsCmdAndWait("AT+CMGS=\"");
gprsCmdAndWait(NUM_TEL);
gprsCmdAndWait("\"\n");
gprsCmdAndWait(sms);
gprsCmdAndWait("\n");
}
void gprsCmdAndWait(char* commandText){ //Функция отправки текста в Serial
GPRS.print(commandText);
delay(300);
}
void initGPRS(){ // функция инициализации GPRS
GPRS.begin(9600);
gprsCmdAndWait("AT+CMGF=1\n"); //выбор режима sms: 1-текстовый,0-цифровой)
gprsCmdAndWait("AT+IFC=1, 1\n");//data flow controll(1, Программный режим (XON/XOFF)).
gprsCmdAndWait("AT+CPBS=\"SM\"\n");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию.
gprsCmdAndWait("AT+CNMI=1,2,2,1,0\n"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю..
}
void onChangedButtonSendSms(){ //Функция проверки состояния кнопки
currBtn = digitalRead(BTN_PIN);
if (prevBtn != currBtn && currBtn == HIGH) {
sendTextMessage("knopka on");
}
if (prevBtn != currBtn && currBtn == LOW) {
sendTextMessage("knopka off");
}
prevBtn = currBtn;
}
может где то и "натупил" - поправьте..
по поводу "boolean currBtn;" - еще в самом начале возник вопрос: почему он в начале не объявлен?
на "вопрос на засыпку" пока не могу ответить.. но пока думаю, а почему бы и не объявить ее в начале, для читабельности кода? Но понимаю что тут есть какой-то подвох...)) "Курю букварь" ))
на "вопрос на засыпку" пока не могу ответить.. но пока думаю, а почему бы и не объявить ее в начале, для читабельности кода? Но понимаю что тут есть какой-то подвох...)) "Курю букварь" ))
И еще, в каких-нибудь книгах по C/C++ аналогичный раздел почитать.
"Почему-бы не объвить в начале" говорите? А чем тут читабельность повысится? Используется в одном месте, объвлена в другом. Читаешь функцию и нужно скролить куда-то вверх что-бы посмотреть тип переменной.
Конфигурацию/настройку мы выносиви вверх, что-бы тот кто откроет скетч - сразу мог увидеть/поправить то что автор скетча задумывал как "настроеченые параметры". А prevBtn - это явно для "внутреней кухни" (поэтому, я вам и говорил что "плохо что она затеслась среди #defint BUTTON_PIN и проч.". Нужно хотя-бы переносами строк ее от "конфигурации" отделить.
Плюс к этому.... вот будете вы копировать функцию куда-то в другой скетч. Вам нужно будет и функцию скопировать, а потом лезть и отдельно копировать все переменные которые она использует. Если же все переменные "внутри функции", то вы "все что нужно" сможете взять одним копи-пастом.
К тому же... а вдруг, тот второй скетч тоже использует переменную с именем currBtn? нужно будет менять имена, что-бы не мешали друг-другу (или вы еще одну подобную функцию напишите, для другой кнопки). Вообщем нужно по возможности избегать "засорения" общего пространства имен. Лучше когда все нужные переменные - внутри функции. Тогда "за пределами функции" - они никому мешать не будут.
Да и функции которые не имеют "побочных эффектов" - легче отлаживать и писать. Функции без побочных эффектов - это функции поведение которых зависит только от того какие параметры вы им подали на вход. Не зависят от состояния других объектов, и не меняют состояния других объектов. Типичный пример
function float x2(float x){
return x*x;
}
Сколько ее не вызывай с параметром x2(3) - всегда будет возвращать 9 :) Вне зависимости от состояния остального мира. Понятно, что такие "феншуйные функции" не всегда можно использовать (например если мы работаем с Serial, то хотим мы того или нет - а мы зависим от его состояния), но если возможно - нужно стремится к этому.
Ладно... это что-то меня уже в область функционального програмирование занесло...
Возвращаясь к нашим баранам, для читабельности и красивости правильней было бы задать вопрос "а почему-бы не перенести объявление prevBtn" тоже внутрь функции? Зачем его "потащили вверх", зачем потребовалась отходить от идеальности? Почему ее не объявили так же как и currBtn?
P.S. И между функциями. Хотя-бы по одной строке пустой вставте. Что-бы не сливались. Чай не на бумаге пишем. Леса экономить нам не нужно.
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
Ну вообщем да. Мы как бы разбиваем задачу на самодостаточные блоки. Стараемся что бы "каждая функция" решала одну задачу. Что-бы ее использование - было очевидно. Отлаживаем их "по отдельности".
Скажем, свою sendTextMessage() мы уже можем, если потребуется, вынести в отедльный упрощенный скетч:
поизгалятся/отладить ее отдельно. И для этого нам даже кнопки не нужны :) После чего, уже обновленный вариант вернуть в "основной скетч".
Когда мы вот эти блоки - отладили. Мы решаем нашу изначальную задачу уже методом "крупно-узловой сборки" :) Мелкие функции - объединяем в более крупные блоки и т.п.
А если нам нужно "посмотреть под микроскопом" на функцию onChangedButtonSendSms,
тоже можем вынести ее в отдельный скетч. Сделать "заглушку", для фунции sendTextMessage() - сделать ее временно "максимально упрощенную версию", достаточную для понимания "правильную инфу в нее послала onChangeButtonSendSms или нет"
#define BTN_PIN 2
void setup(){
Serial.begin(9600);
}
void loop(){
onChangeButtonSendSms();
}
void onChangeButtonSendSms(){ // функция поведение которой исследуем
.....
}
// отладочная "функция заглушка"
void sendTextMessage(char* sms){
Serial.print(millis()); // выведем время когда функция была вызвана, что-бы лог было легче читать
Serial.print(": sendTextMessage called:"); // что-бы было понятно какая функция была вызвана
Serial.println(sms); // и с каким параметром
}
Понятно, что это не "постоянно нужно так делать" (выносить функции в отдельный скетч). Это просто один из "приемов" в процессе написания отладки.
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
#define GPRS Serial
#define BTN_PIN 2
boolean prevBtn = LOW;
boolean currBtn;
char NUM_TEL[]="+78*************";
void setup(){
initGPRS(); //вызов функции инициализации GPRS
}
void loop()
{
onChangedButtonSendSms(); // вызов Функции проверки изменения кнопки
}
void sendTextMessage(char* sms) { // Функция отправки SMS
gprsCmdAndWait("AT+CMGS=\"");
gprsCmdAndWait(NUM_TEL);
gprsCmdAndWait("\"\n");
gprsCmdAndWait(sms);
gprsCmdAndWait("\n");
}
void gprsCmdAndWait(char* commandText){ //Функция отправки текста в Serial
GPRS.print(commandText);
delay(300);
}
void initGPRS(){ // функция инициализации GPRS
GPRS.begin(9600);
gprsCmdAndWait("AT+CMGF=1\n"); //выбор режима sms: 1-текстовый,0-цифровой)
gprsCmdAndWait("AT+IFC=1, 1\n");//data flow controll(1, Программный режим (XON/XOFF)).
gprsCmdAndWait("AT+CPBS=\"SM\"\n");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию.
gprsCmdAndWait("AT+CNMI=1,2,2,1,0\n"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю..
}
может где то и "натупил" - поправьте..
Да не... "тупостью" это не назовешь. Обычный процесс разбирательства.
Ну во первых... а где же здоровая лень? Если вас не устраивало, что gprsCmdAndWait всегда посылало в конце переводы строки, а вам хочется "когда посылать, а когда и нет". То.. можно было либо ввести ей второй параметр, который будет указыать нужно ли переводит строку, либо сделать две функции
gprsCmdAndWait(char* sms){
GPRS.print(sms);
delay(100);
}
gprsCmdAndWaitln(char* sms){ // то же самое, только с переводом строки
GPRS.println(sms)
delay(100:
}
И пользоватся ими "по необходимости". Возможно так было-бы лучше, чем во всех строках добалять \n ... хотя... две похожие функции - тоже не симпатично (но что-то не приходит в голову как тут улучшить). Вообщем считайте это не "так было лучше", а "можно было еще и так" :)
К тому же.... я вот говорил вам, что "отделите пустой строкой в коде" строки
Что-бы было видно, что это формирование "одной строки" команды. А не "несколько команд". Тогда бы вы увидели, что нам не нужно "сворачивать" все в использование gprsCmdAndWait() . Так как это даст нам лишние задержки посреди процесса отправки команды. Зачем? Воспринимайте что gprsCmdAndWait() - это "завершение команды и ожидание ее выполнения". Тогда станет ясно, что с помощью нею, можно было заменять (что-бы логика не поменялась) только последние две строки.
В этом случае у нас не поменялась логика по сравнению с оригинальной. Не пришлось переделывать саму gprsCmdAndWait (в ней так и остался GPRS.println), пожелание "все команды в итоге проходят через gprsCmdAndWait() - тоже выполнилось. Мы просто... часть команды подалить "чуть раньше", помогли gprsCmdAndWait(). Но, в итоге - все равно сделали ее вызов.
Но вы "зацепили", по сравнению с оригинальной логикой. Еще одну важную вещь (я так думаю, точно не скажу так как не знаю что у вас за шилд и какие там команды). В оригинале признаком окончания текста sms-ски была посылка символа с десятичным кодом 26. А теперь, в конце sms-ски вы послаете \n. Опасаюсь что шилд не поймет этого. Подумает что просто вы хотите написать вторую строку sms-ски :(
А что же делать, для 26 - нет специально \БУКВА, но есть выход :) Если нам в строке нужно использовать символ с каким-то кодом и код мы знаем, то мы можем написать gprsCmdAndWait("\xNN"); где NN - код символа в шестнадцатеричном виде. Возмем к примеру символ новой строки \n и представим себе что у него нет специальной буквы :) Не страшно, заглядывам в http://www.asciitable.com/ и видим что его десятичный код 10 (то есть можно было написать char(10) и это было-бы то же самое) или раз нам нужно в строке использовать, то смотрим во вторую колонку берем шестнадцатеричное значение и пишем "\x0A";
Так что... нам нужно взять 26, перевести его в шестнадцатеричное значение и вставить с помощью \x... Можно через ссылку-табличку выше, а можно.... Взять стандартный калькулятор виндовса :) В нем нажать ALT-3 (или поййти в меню View/Programmer) - переключить его в програмерский режим.
После чере переключаемся в Dec, набираем наше число 26, переключаемся в Hex - и видим его 16-тиричное значение.
Кстати ниже - есть еще вариант Bin - это если нам вдруг захотелось в двоичном виде его увидеть.
А есть еще и третий вариант :) Вдруг нет у нас калькулятора :) Но есть ардуина :) А про функцию print, мы уже читали внимательно. Тогда:
Не соглашусь.
Life23 - пока не смотри
if (prevBtn != currBtn) { switch(currBtn) { case(HIGH): i = 0; break; case(LOW): i=1; break; default: //Hm.... } } sendTextMessage(i);Вот.... :)
Теперь, нужно пойти в раздел Программирование Ардуино | Аппаратная платформа Arduino
И почитать раздел "типы данных". Ознакомится со всеми ими. Это как ключи/отвертки у мастера. Он должен знать "какие у него есть" и старатся подбирать наиболее подходящий по размеру в каждой ситуации.
Ознакомитесь - попробуйте заменить int на что-то более экономное к памяти. То есть - в данном случае это не критично, но привыкать использовать "минимально подходящий" тип - нужно. У ардуины не так много ресурсов, что-бы разбрасываться ими не думая :)
Не соглашусь.
Не соглашайтесь. Только switch для bool переменной - это дуристика. Сам же //Hm.. написали.
У вас вышел более громоздкий, трудней читаемый, трудней модифицируемый код (а ну как нужно будет усложнить условие?)
Если очень хочется "повыеживатся" (Life23 - не читай, так делать - плохой стиль), то можно было вообще упаковатся в одну строку
Но за такой код - нужно по рукам бить. Потому что тот кто будет читать его после тебя (или ты сам через годик) - мозги расплавит, что-бы понять "что тут происходит".
Так что те if-ы, в данной ситуации - самое нормальное решение.
я уже понял ошибку.. надо было использовать byte - 8бит занимает. А int у меня сожрал целых 2байта..
я уже понял ошибку.. надо было использовать byte - 8бит занимает. А int у меня сожрал целых 2байта..
Верно.
А еще вы не нужно потратили два байта int btnPin = 2; Плюс потеряли пару тактов на чтении переменных.
Помните как для светиков мы пины именовали?
Вот. И для кнопки - можно так же. Хотя признаю, что в инете, в куче примеров с маниакальным упорством пихают номера пинов в переменные, да еще в int.
понял. "int btnPin = 2" удалил и заменил на #define btnPin 2
А еще перечитайте сообщение #18 (про хорошую идею) и попробуйте угадать что мне не нравится в строке вида:
Это был просто пример, человек, насколь мне видится, с удовольствием учится, вот поэтому я и предлагал его. Но чуствуй что "у семи нянек дитя без глазу", я тут только мешаюсь да и Ваши знания в ардуино мои превосходят, так что встревать больше не буду.
ПС: на вкус и цвет все фломастеры разные, в данном конкретном примере я бы тоже не свитч использовал а иф вложенный, но тут как говорится кому что больше нравится :)
понял. "int btnPin = 2" удалил и заменил на #define btnPin 2
Ага. Только лучше уж однотипно это делать c RED_LED_PIN. Скажем BTN_PIN назвать.
Тогда, читая код, вы сразу будете понимать "если что-то названо целиком большими буквами" - это #define. Изменять в коде его значение - мы не можем. Если же оно "с маленькой буквы", то это переменная.
Вообщем если придерживатся такой стилистики, то не глядя в объявления всегда можно догадатся что bntPin - это переменная, а BTN_PIN - это дефан :)
Такой подход - снижает шансы на ошибку.
Это был просто пример, человек, насколь мне видится, с удовольствием учится, вот поэтому я и предлагал его.
Ага. И пусть после этого кто-то скажет что новичков пинают за незнание предмета (на форуме не одну подобную ветку найти можно).
P.S. Эх... а за название темы "помогите новичку" - все таки нужно было попинать :)
P.S. Эх... а за название темы "помогите новичку" - все таки нужно было попинать :)
ну простите.. стыдно.. честно.. ))
P.S. Эх... а за название темы "помогите новичку" - все таки нужно было попинать :)
ну простите.. стыдно.. честно.. ))
разочарован, что такой замах пропал даром. .
ж)
Ну, так чем плоха строка
догадались?
самим "sendTextMessage"? лучше будет "smsIndex"?
что называеться "слИпил" коды.. Страшно вылаживать, так как понимаю что в void loop полный бред и надо еще изучать, но все таки попробую..
#define GPRS Serial #define BTN_PIN 2 #define LOAD_PIN 4 boolean isStringMessage = false; boolean prevBtn = LOW; char* myStrings[]={"Knopka ON", "Knopka OFF"}; void setup() { GPRS.begin(9600); GPRS.print("AT+CMGF=1\r"); delay(300); GPRS.print("AT+IFC=1, 1\r"); delay(300); GPRS.print("AT+CPBS=\"SM\"\r"); delay(300); GPRS.print("AT+CNMI=1,2,2,1,0\r"); delay(500); } String currStr = ""; // Переменная принимает значение True, если текущая строка является сообщением void loop() { boolean currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { smsIndex(0); } if (prevBtn != currBtn && currBtn == LOW) { smsIndex(1); } prevBtn = currBtn; if (!GPRS.available()) return; char currSymb = GPRS.read(); if ('\r' == currSymb) { if (isStringMessage) { //если текущая строка - SMS-сообщение, //отреагируем на него соответствующим образом if (!currStr.compareTo("Load on")) { digitalWrite(LOAD_PIN, HIGH); } else if (!currStr.compareTo("Load off")) { digitalWrite(LOAD_PIN, LOW); } isStringMessage = false; } else { if (currStr.startsWith("+CMT")) { //если текущая строка начинается с "+CMT", //то следующая строка является сообщением isStringMessage = true; } } currStr = ""; } else if ('\n' != currSymb) { currStr += String(currSymb); } } void smsIndex(byte sms) { // Устанавливает текстовый режим для SMS-сообщений GPRS.print("AT+CMGF=1\r"); delay(100); // даём время на усваивание команды GPRS.println("AT + CMGS = \"+79************\""); delay(100); GPRS.println(myStrings[sms]); delay(100); // Отправляем Ctrl+Z, обозначая, что сообщение готово GPRS.println((char)26); }самим "sendTextMessage"? лучше будет "smsIndex"?
Нет. Я же намекал что перечитат нужно :)
Вот если постороннему человеку (или вам через пол года) показать строку
или
В каком варианте человек быстрее догадается, что эта строчка делает?
PS:
MSG - message - сообщение
IDX - index - индекс
SUCCESS - удачно, подтверждение...
что называеться "слИпил" коды.. Страшно вылаживать,
Значит - рано :) Не дополировали еще "детали" что-бы они легко и без напряга друг к другу подошли :)
Может даже работать будет (не вникал), но... вникать-то тяжело. "Не причесан" еще код. Когда "дочешем его" - тогда и "как слепить" - будет самоочевидно :)
Да и более "устойчивым к разным ситуациям" он, я думаю, станет :)
Если конечно хотите тратить на это время ;)
Компилятор проглотил.. но очивидно, что работает на так как надо.. ))
поставил задачу - хочу дойти до конца! )
буду "чесать" (с Вашей, конечно же, помощью)!
я правильно Вас понял: надо ввести еще значения функции, да бы подтвердить какое именно сообщение отправленно и отправленно ли оно?
я правильно Вас понял: надо ввести еще значения функции, да бы подтвердить какое именно сообщение отправленно и отправленно ли оно?
Тут уже я вас не понял :(
Нет. Я иммел ввиду, что "любые цифры в коде" - это зло. "Голая цифра" - ничего не говорит читающему (в большинстве случаев, в любом правиле есть исключения). Даже термин есть устоявшийся "магические цифры". Это какие-то цифры, забитые прямо в код. С этими цифрами код работает. Но почему эти цифры именно такие - понятно только автору. Поэтому они "магические". Наличие таких цифр - признак кода который тяжело сопровождать.
Поэтому, где возможно - стараемся заменять их константами/дефайнами
Вместо
пишем
И где-то вверху объявляем
С точки зрения компилятора - это вообще идентичные скетчи.
С точки зрения читающего - "О... сразу понятно что послали сообщение о том что кнопку нажали/отпустили".
С нашей точки зрения - чуть-чуть больше кода набрать нужно :) Но IMHO оно того стоит. Не велика расплата за читабельный код (потом не раз себя похвалим).
тьфу.. а я уже полез в высшую материю..
Вернулся к своему последнему работающему коду.
Исправил все замечания:
#define GPRS Serial #define BTN_PIN 2 #define MSG_IDX_BUTTON_PRESSED 0 #define MSG_IDX_BUTTON_RELEASED 1 boolean prevBtn = LOW; char* myStrings[]={"BUTTON_PRESSED", "BUTTON_RELEASED"}; void setup() { GPRS.begin(9600); delay(100); GPRS.println("AT+CMGF=1\r"); delay(300); GPRS.println("AT+IFC=1, 1\r"); delay(300); GPRS.println("AT+CPBS=\"SM\"\r"); delay(300); GPRS.println("AT+CNMI=1,2,2,1,0\r"); delay(500); } void loop() { boolean currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { sendTextMessage(MSG_IDX_BUTTON_PRESSED); } if (prevBtn != currBtn && currBtn == LOW) { sendTextMessage(MSG_IDX_BUTTON_RELEASED); } prevBtn = currBtn; } void sendTextMessage(byte sms) { GPRS.print("AT+CMGF=1\r"); delay(100); GPRS.println("AT + CMGS = \"+79************\""); delay(100); GPRS.println(myStrings[sms]); delay(100); GPRS.println((char)26); }Здорово.
Ну сами строки (текст sms) - можно было и оставить "Knopka On", "Knopka Off". Это уже "то что видит человек", поэтому главное - чтобы пользователю было понятно. А обычному человеку подчеркивание между словами - немного не привычно. Не програмеру - привычней видеть пробелы :) (вот... уже и у вас мозги начинаю на програмерски бекрень наклонятся :)
А теперь. Вы наверное решите что я издеваюсь :) - выкидываем эти MSG_IDX_BUTTON_PRESSED и MSG_IDX_BUTTON_RELEASED ;)
Просто нужно было показать, что "магические цифры" - зло. И как с ними бороться. Использование #define для этого - обычная практика.
Но... если отказатся от дополнительного массива myStrings[] (тоже, кстати, слишком общное название. ничего не говорящие, что-то типа smsMessages[] - было бы удачней), так вот... если мы вообще от него откажемся, то у нас вообще отпадет надобность в цифрах. Исчезнет массив - исчезнут индексы, не будет индексов - не нужны цифры, не нужны цифры - нечего заменить на #define-ны :)
Вы говорили что "параметры функции" уже не так страшно :)
Давайте попробуем переделать функци sendTextMessage() что-бы она принимала не индекс, а строку. Что-бы параметр был не цифровой, а текстовый.
Что-бы пользоватся ей можно было так:
sendMessage("Hello from Arduino");Подсказка: нужно поменять тип параметра функции с int на.... что-то очень похожие на то как вы объявляли массив myStrings[]. Только там вы объявили "массив строк", а тут вам нужно, даже проще "объявить строку". Параметр типа строка, а не int как сейчас
#define GPRS Serial #define BTN_PIN 2 boolean prevBtn = LOW; void setup() { GPRS.begin(9600); delay(100); GPRS.println("AT+CMGF=1\r"); delay(300); GPRS.println("AT+IFC=1, 1\r"); delay(300); GPRS.println("AT+CPBS=\"SM\"\r"); delay(300); GPRS.println("AT+CNMI=1,2,2,1,0\r"); delay(500); } void loop() { boolean currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { sendTextMessage("knopka on"); } if (prevBtn != currBtn && currBtn == LOW) { sendTextMessage("knopka off"); } prevBtn = currBtn; } void sendTextMessage(String sms ) { GPRS.print("AT+CMGF=1\r"); delay(100); GPRS.println("AT + CMGS = \"+79************\""); delay(100); GPRS.println(sms); delay(100); GPRS.println((char)26); }с гордо подной головой пошел спать.. ))))
update: еще раз перечитав ветку, понял, что это можно было сделать уже давно.. и намеки были неоднократно..
простите - я только учусь (с) ))
p.s.: к стати, хоть легче стало читать код "обычному человеку", а размер скетча в двоичном коде от предыдущего вырос почти в два раза...
Ну, во первых тут уже вопрос "приоритетов". Что лучше купить камень более жирный (который стоит на $0.5 дороже, а иногда даже дешевле) или потратить неделю на ковыряния "в кошмаре нечитабельном".
Во вторых при потребности, точечно оптимизировать красивый код гораздо легче, чем код в котором изначально "эфективность" ставилась во главу угла. На маленьком скетче "байты выжимать ценой читабельности" - еще получится. А вот на большом - все равно выйдет "монстр прожорливый". Просто потому что вы потеряете над ним контроль (начнете больше ошибок делать, выделять память "на всякий случай", потому что не возможно понять "можно повторно использовать уже существующие переменные или нет" и т.д. и т.п.
В третьих. Код у вас вырос не потому что вы добавили ему читабельности. А потому что.... тип параметра не удачно выбрали :)
Я же вам писал подсказку "тип параметра должен бы очень похожим на то как вы объявляли myStrings[]". Был там тип String? Небыло. А тут - появился :) Вот он вам и насрал в кашу.
Выше по ветке я советовал вам прочитать раздел доки "Типы данных" целиком (если читали - еще раз. я сам регулярно почитываю ;) Там вы увидите что у нас есть два вида строк.
string и String
Вы воспользовались String. Это чисто "Ардуина фишка". В стардатных сях таких строк нет. Не хочу глубоко вдаватся, тема холиварная, но лично мое мнение что в микроконтроллерах String - это зло. На одну бяку (размер) - вы уже наступили. А есть еще :)
А нужно было воспользоватся стандартными c-шными строками. string. Поменяйте объявление параметра функции на "char* sms" или "char sms[]" и посмотрите на размер скетча. Будете приятно удивлены :)
Вообщем мой вам совет: не ведитесь на простоту использования String. Осваивайте обычные сишный строки. Никуда вы от них не денетесь. Все равно везде где что-то сложней 10-ти строк - будете на них натыкатся.
Тема сишных строк внешне простая, но ... имеет подводные камни. В рамках форума - с ними не просто разобратся досконально. Так что советую взять учебничек (а лучше несколько) по C/C++ и покопатся в этих разделах (там еще на указатели потребуется помедитировать). Вообщем "строки/указатели" - потребуют какоего времени что-бы стать с ними "на Ты". Но это будет полезно потраченное время. Вообщем "задание на каникулы" вам:)
хм.. действительно код уменьшился в размере с char... :)
я и начинал как раз с char. но не хватило ума потом как правильно он объявляеться в "GPRS.println(sms);" (полез в дебри), а на сомом деле все просто. Да еще и лишнюю скобку втулил.. сбился с толку совсем.. За то теперь буду знать что такое String. ))))
раздел доки "Типы данных" я постоянно перечитываю.. но пока не попробуешь - бесполезно.. практика великое дело..
OK. Поехали дальше :)
Что плохого в строке
GPRS.println("AT + CMGS = \"+79************\"");???
Подсказка: а не нужно ли ее разбить на три GPRTS.print и..... ?
GPRS.print("AT+CMGS="); GPRS.print(NumTel); GPRS.print((char)13);универсальность.. епт... ))))хотя лучше наверно так:
#define NumTel +79************ GPRS.print("AT+CMGS="); GPRS.print((char)34); GPRS.print(NumTel); GPRS.print((char)34); GPRS.print((char)13);?
Ну, очень близко. Только не скомпилируется :)
Явно не до конца поняли что такое define. (и правило именовайние - нарушили).
Что происходит с #defin-ом. В момент компиляции (важно, не в момент исполнения), даже "до компиляции", компилятор ищет по коду этот NumTel, если где-то находит - подставляет его туда и потом компилирует. Именно поэтому, с точки зрения компилятора
то же самое что и
Оба варианта - скомпилируются, с точностью до байта в одно и тоже. Просто потому, что компилятор первый вариант, незаметно, преобразует во второй.
Так что "для человека" - это разный код. Для компилятора - тот же самый :)
Темперь смотрим, что у вас получилось. С вашим дефайном. КОгда он подставиться, то вы выйдет
Что не очень хорошо. Не дай бог в номере попадутся дефисы, круглые скобки.... получим кучу ругани что "цифра какая-то не непонятная". Поэтому мы должны делать print, не числа, а строки (как и было в изначальном). То есть - нам нужно, что-бы, после подстановки #define получилось что-то типа:
GPRS.print("+79********");Тогда это строка, а в строке могут быть и дефисы и проч. Что хотим. Компилятор ругатся не будет.
Вообщем - потеряли вы кавычки. Ваш дефайн должен был выглядить так:
Но... не буду счас сильно вдаватся почему (память/указатели и т.п.), но в отличие от цифр, строки, особенно длинные лучше выносить в конфигурацию не с помощью #define, а с помощою обычных переменых. Ну просто такое правило :) Номера пинов, время задержки и проч. - объевляем #define-нами, строки - переменными.
Итого
Поехали далее.
GPRS.print("AT+CMGS="); GPRS.print((char)34);Будет рабоать, но ... изначально же было сделано через экранивание двойной кавычки. Кто там помнит что такое 34? Поэтому одна строка
GPRS.print("AT+GMGS=\"");вместо двух - будет лучше выглядить (косая черта означает "двойная ковычка это именно символ двойной кавычки, а не признак конца строки". Мы ее "заэкранировали" с помощью косой черты. Сказали компилятору что "следующий символ" - просто символ. Не пытайся найти в нем смысл :)
То же самое и с завершением.
Вообщем, вся эта лабуда у нас в итоге будет примерно такой
char numTel[]="+78*************"; ...... GPRS.print("AT + CMGS = \""); GPRS.print(NumTel); GPRS.println("\"");Кстати, если уже хотелось, то вместо
GPRS.print((char)34);Можно писать
"въехал.." и за нотировал ваш пост себе в блокнотик )
но почему-то оба моих варианта компилировались без проблем.
в первом варианте, в #define я ж в скобки вписал номер телефона ))))) а вот без скобок компилятор меня послал )))
все это - зло! )) я уже понял )
а вот косая в (CMGS = \"+79************\"") меня и сбила с толку.. теперь буду знать о ней..
"въехал.." и за нотировал ваш пост себе в блокнотик )
но почему-то оба моих варианта компилировались без проблем.
в первом варианте, в #define я ж в скобки вписал номер телефона ))))) а вот без скобок компилятор меня послал )))
все это - зло! )) я уже понял )
а вот косая в (CMGS = \"+79************\"") меня и сбила с толку.. теперь буду знать о ней..
Во первых не "скобки", а "двойные кавычки" :) Во вторых, не послать, без кавычек, он мог только если номер действительно был только из цифр и знака плюс.
а вот косая в (CMGS = \"+79************\"") меня и сбила с толку.. теперь буду знать о ней..
Кстати тут вы потеряли открывающую двойную кавычку. Как компилятор поймет где строка начинается?
OK. Давайте текущий скетч что у вас получился.
И даже не найдетесь что я не смогу придумать что в нем еще можно улучшить :)
И даже не найдетесь что я не смогу придумать что в нем еще можно улучшить :)
"-а может быть не надо!?" (с), ))
#define GPRS Serial #define BTN_PIN 2 boolean prevBtn = LOW; char NUM_TEL[]="+78*************"; void setup() { GPRS.begin(9600); delay(100); GPRS.println("AT+CMGF=1\r"); delay(300); GPRS.println("AT+IFC=1, 1\r"); //data flow controll(1, Программный режим (XON/XOFF)). delay(300); GPRS.println("AT+CPBS=\"SM\"\r"); //выбрать как основную память сим-карту. Данный режим стоит по умолчанию. delay(300); GPRS.println("AT+CNMI=1,2,2,1,0\r"); delay(500); } void loop() { boolean currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { sendTextMessage("knopka on"); } if (prevBtn != currBtn && currBtn == LOW) { sendTextMessage("knopka off"); } prevBtn = currBtn; } void sendTextMessage(char* sms) { GPRS.print("AT+CMGS=\""); GPRS.print(NUM_TEL); GPRS.println("\""); GPRS.print(sms); GPRS.println((char)26); }Надо :)
Ну во первых я бы порекомендовал, между строчками 33 и 34 вставить пустую строку. "Отбить их визуально". Что-бы четче было видно что 31,32,33 - формируют одну строку. Ну или вообще написать их "в одну строчку", как я сделал в #78 в конце. Но это даже "на замечание" не тянет. Скорее "дело вкуса".
OK. По "замечаниям". Давайте вы еще раз прочитаете Serial.print и Serial.println . Вникните в их различие. И что они делают. Особенно println. А потом посмотрите свой код... и подумаем, а зачем там в строках символ \r? Причем попадается он и в print и в println. Он там действительно нужен? Если нет - убрать, если нужен - единообразить все. А то "то так, то этак" :)
"Отбить их визуально".. АААААаа... я чуть было не грохнул ардуинку о пол.. )) но вовремя прочитал: Но это даже "на замечание" не тянет. Скорее "дело вкуса".)))))
Serial.println - уже автоматически вставляет \r
p.s. Без dealy() в строках 31-35 - ох как он начал строчит дребезг в serial... ((
Кстати - молодец что начал коментировать то что "может забыться" и не очевидно и с кода. Я про коменты к строкам (12), ( 14)
Кстати, вот так же, чуть-чуть "доофрмить" можно и шапку. Не перемешивать там, через строчку, переменные "для работы" и "то что конфигурирем". Плюс - комментом выделить, что-бы кому-то другому было легче разбиратся.
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете." (С) Стив Макконнелл
OK. Считаем что с println и \r вы разобрались и сами поправите.
Едем дальше.
Что-то у нас с задержками после отсылки команды - полная чехарада. То 100, то 300, то 500. То исчезают, то появляются :) Да и каждый раз писать по две строчки - лениво же :)
Давайте тоже это дело единообразим. Сделаем вспомогательную функцию, которая будет отсылать команду gprs-су, и ждать пока он ее выполнит
void gprsCmdAndWait(char* commandText){ GPRS.println(commandText); delay(100); }И сразу, много где мы сможем вместо двух строчек написать одну :)
P.S. Пока необходимость выделять это в отделную функцию "притянута за уши". Две строки - можно было-бы и потерпеть, но.... чуть позже увидим что "выносить в отдельную функцию" - таки было нужно.
"Отбить их визуально".. АААААаа... я чуть было не грохнул ардуинку о пол.. ))
Ну вы уже поняли, что "код не пишется", код "выращивается" :) Кстати вот так "то тут поправили, то там", потом "чуток дописали нового функционала абы работал", потом опять "логику не меняем, но делаем что-бы код красивым был" (рефакторинг - название этого процесса). Так вот - это обычный процесс разработки. Не только у новичков.
Если совсем не вмоготу - можете вот тут пар выпустить Помогите новичку! | Аппаратная платформа Arduino :)
А вообще - что же вы хотите? ;) За пару дней - тут у вас неплохой такой "интенсивчик" получился. Не каждый бы выдержал. В институте - этот объем легко бы на семестр растянулся. Конечно тут мы "сильно по верхам" пробежали, но круг новых понятий - не такой уж и маленький получился.
Немного оклемавшись от вскрытия моей машины какими-то уродами - иду дальше:
#define GPRS Serial #define BTN_PIN 2 boolean prevBtn = LOW; char NUM_TEL[]="+78*************"; void setup() { GPRS.begin(9600); gprsCmdAndWait("AT+CMGF=1"); //выбор режима sms: 1-текстовый,0-цифровой) gprsCmdAndWait("AT+IFC=1, 1");//data flow controll(1, Программный режим (XON/XOFF)). gprsCmdAndWait("AT+CPBS=\"SM\"");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию. gprsCmdAndWait("AT+CNMI=1,2,2,1,0"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю.. } void loop() { boolean currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { sendTextMessage("knopka on"); } if (prevBtn != currBtn && currBtn == LOW) { sendTextMessage("knopka off"); } prevBtn = currBtn; } void sendTextMessage(char* sms) { GPRS.print("AT+CMGS=\""); GPRS.print(NUM_TEL); GPRS.println("\""); delay(100); GPRS.print(sms); delay(100); GPRS.println((char)26); } void gprsCmdAndWait(char* commandText){ GPRS.println(commandText); delay(300); }склонный к насилию психопат - пускай пока подождет... не стал пока комментировать код и разделять, что бы текста пока было по меньше, пока "въеду" в сам смысл работы кода.
p.s.: по поводу "Программный режим (XON/XOFF)" читал много, что не стоит его использовать так как он не идеален и много с ним проблем. Глубоко не вникал - чисто для размышления..
Сорри. Ближе к вечеру смогу уделить время.
А так: правда setup() стал симпатичней? ;) еще бы и в sendTextMessage тоже применить это gprsCmdAndWait() :)
Правда там так идеально он уже не применится и чуток мозг похмурить нужно будет что-бы его воткнуть (вообщем если не получится - покажу, только чуть позже). Но, кроме красоты, применение специальной функции для отсылки любой команды на gprs (и никогда не послать в ее обход) дает нам еще одно преимущество: если нам потребуется какая-то дополнительная логика при посылке команд (например "мигнуть диодом при этом", показать что ардуино что-то командует шилду) то это изменение можно внести в одно место. в функцию gprsCmdAndWait(), а не по всему коду лазить и искать где у нас команды посылаются.
Вообщем попробуйте, если сразу не получится - подождите до вечера :)
И еще... можно уже, потихоньку, начинать готовить это скетч к тому что он будет "интегрироватся с другим скетчем" (преставим что пока не знаем с каким именно).
Как вы уже поняли интеграция двух скетчей сводится, грубо говоря, к слиянию функций setup() и loop(). В идеальном случае какие setup() и loop() проще всего сливать? Конечно полностю пустые :) К сожалению - мы живем в реальном мире и пустые setup() и loop() - у нас небудут никогда. Но стремится к совершенству - нужно :) Пойдем на компромис. Постараемся их сделать если не пустыми, то хотя-бы как можно более тривиальными. Для этого всю "развесистую" логику мы вынесем в отдельные функции (причем еще и имена подберем так, что-бы сразу было понятно что делает эта "Развесистая логика") и будет из loop() и setup() вызывать эти функции.
Что-нибудь поняли из этого потока сознания? ;) Если нет, тогда более конкретно:
Все что сейчас в setup() - выносим в функцию с именем скажем initGPRS(); , вче что сейчас в loop() - выносим в функцию onChangedButtonSendSms();
Что-бы наши setup() и loop(), в итоге приняли вид:
setup(){ initGPRS(); } loop(){ onChangedButtonSendSms(); }Глянув на такие setup() и loop() сразу можно понять, в общих чертах, чем же занимается скетч. Даже если нет никаких комментариев :)
да, действительно, setup стал на много проще.
Именно такого ответа я и боялся.. )) потому как сначала хотел применить данный финт с sendTextMessage ))) много полезло багов..
Попробую. До вечера еще время есть ) все таки когда сам до чего-то до шел - легче запомнить.. и понимаешь лучше - что к чему..
"мигнуть диодом при этом" - вполне полезная штука! визуальное оповещение работы устройства никогда не помешает.. не говоря уже о применение в других устройствах.
И еще чуть-чуть забегу вперед. Что-бы дальнеший ход мысли было легче понять.
Вопрос на засыпку: почему currBtn мы объявляли внутри функции, а prevBtn вверху скетча? Это просто "неакуратность" или "так и нужно"? Появляется ли из-за этого какая-то разница в поведении/свойствах этих двух переменных? Или можно объявлять где удобней автору?
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
#define GPRS Serial #define BTN_PIN 2 boolean prevBtn = LOW; boolean currBtn; char NUM_TEL[]="+78*************"; void setup(){ initGPRS(); //вызов функции инициализации GPRS } void loop() { onChangedButtonSendSms(); // вызов Функции проверки изменения кнопки } void sendTextMessage(char* sms) { // Функция отправки SMS gprsCmdAndWait("AT+CMGS=\""); gprsCmdAndWait(NUM_TEL); gprsCmdAndWait("\"\n"); gprsCmdAndWait(sms); gprsCmdAndWait("\n"); } void gprsCmdAndWait(char* commandText){ //Функция отправки текста в Serial GPRS.print(commandText); delay(300); } void initGPRS(){ // функция инициализации GPRS GPRS.begin(9600); gprsCmdAndWait("AT+CMGF=1\n"); //выбор режима sms: 1-текстовый,0-цифровой) gprsCmdAndWait("AT+IFC=1, 1\n");//data flow controll(1, Программный режим (XON/XOFF)). gprsCmdAndWait("AT+CPBS=\"SM\"\n");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию. gprsCmdAndWait("AT+CNMI=1,2,2,1,0\n"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю.. } void onChangedButtonSendSms(){ //Функция проверки состояния кнопки currBtn = digitalRead(BTN_PIN); if (prevBtn != currBtn && currBtn == HIGH) { sendTextMessage("knopka on"); } if (prevBtn != currBtn && currBtn == LOW) { sendTextMessage("knopka off"); } prevBtn = currBtn; }может где то и "натупил" - поправьте..
по поводу "boolean currBtn;" - еще в самом начале возник вопрос: почему он в начале не объявлен?
на "вопрос на засыпку" пока не могу ответить.. но пока думаю, а почему бы и не объявить ее в начале, для читабельности кода? Но понимаю что тут есть какой-то подвох...)) "Курю букварь" ))
на "вопрос на засыпку" пока не могу ответить.. но пока думаю, а почему бы и не объявить ее в начале, для читабельности кода? Но понимаю что тут есть какой-то подвох...)) "Курю букварь" ))
Да. Это правильное действие. На всякий случай вот: Переменные | Аппаратная платформа Arduino
И еще, в каких-нибудь книгах по C/C++ аналогичный раздел почитать.
"Почему-бы не объвить в начале" говорите? А чем тут читабельность повысится? Используется в одном месте, объвлена в другом. Читаешь функцию и нужно скролить куда-то вверх что-бы посмотреть тип переменной.
Конфигурацию/настройку мы выносиви вверх, что-бы тот кто откроет скетч - сразу мог увидеть/поправить то что автор скетча задумывал как "настроеченые параметры". А prevBtn - это явно для "внутреней кухни" (поэтому, я вам и говорил что "плохо что она затеслась среди #defint BUTTON_PIN и проч.". Нужно хотя-бы переносами строк ее от "конфигурации" отделить.
Плюс к этому.... вот будете вы копировать функцию куда-то в другой скетч. Вам нужно будет и функцию скопировать, а потом лезть и отдельно копировать все переменные которые она использует. Если же все переменные "внутри функции", то вы "все что нужно" сможете взять одним копи-пастом.
К тому же... а вдруг, тот второй скетч тоже использует переменную с именем currBtn? нужно будет менять имена, что-бы не мешали друг-другу (или вы еще одну подобную функцию напишите, для другой кнопки). Вообщем нужно по возможности избегать "засорения" общего пространства имен. Лучше когда все нужные переменные - внутри функции. Тогда "за пределами функции" - они никому мешать не будут.
Да и функции которые не имеют "побочных эффектов" - легче отлаживать и писать. Функции без побочных эффектов - это функции поведение которых зависит только от того какие параметры вы им подали на вход. Не зависят от состояния других объектов, и не меняют состояния других объектов. Типичный пример
function float x2(float x){ return x*x; }Сколько ее не вызывай с параметром x2(3) - всегда будет возвращать 9 :) Вне зависимости от состояния остального мира. Понятно, что такие "феншуйные функции" не всегда можно использовать (например если мы работаем с Serial, то хотим мы того или нет - а мы зависим от его состояния), но если возможно - нужно стремится к этому.
Ладно... это что-то меня уже в область функционального програмирование занесло...
Возвращаясь к нашим баранам, для читабельности и красивости правильней было бы задать вопрос "а почему-бы не перенести объявление prevBtn" тоже внутрь функции? Зачем его "потащили вверх", зачем потребовалась отходить от идеальности? Почему ее не объявили так же как и currBtn?
P.S. И между функциями. Хотя-бы по одной строке пустой вставте. Что-бы не сливались. Чай не на бумаге пишем. Леса экономить нам не нужно.
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
Ну вообщем да. Мы как бы разбиваем задачу на самодостаточные блоки. Стараемся что бы "каждая функция" решала одну задачу. Что-бы ее использование - было очевидно. Отлаживаем их "по отдельности".
Скажем, свою sendTextMessage() мы уже можем, если потребуется, вынести в отедльный упрощенный скетч:
#define GPRS Serial setup(){ GPRS.begin(9600); sendTextMessage("Test message 1"); delay(1000); sendTextMessage("Test message 2"); } void loop(){ } void sendTextMessage(char* sms){ .... }поизгалятся/отладить ее отдельно. И для этого нам даже кнопки не нужны :) После чего, уже обновленный вариант вернуть в "основной скетч".
Когда мы вот эти блоки - отладили. Мы решаем нашу изначальную задачу уже методом "крупно-узловой сборки" :) Мелкие функции - объединяем в более крупные блоки и т.п.
А если нам нужно "посмотреть под микроскопом" на функцию onChangedButtonSendSms,
тоже можем вынести ее в отдельный скетч. Сделать "заглушку", для фунции sendTextMessage() - сделать ее временно "максимально упрощенную версию", достаточную для понимания "правильную инфу в нее послала onChangeButtonSendSms или нет"
#define BTN_PIN 2 void setup(){ Serial.begin(9600); } void loop(){ onChangeButtonSendSms(); } void onChangeButtonSendSms(){ // функция поведение которой исследуем ..... } // отладочная "функция заглушка" void sendTextMessage(char* sms){ Serial.print(millis()); // выведем время когда функция была вызвана, что-бы лог было легче читать Serial.print(": sendTextMessage called:"); // что-бы было понятно какая функция была вызвана Serial.println(sms); // и с каким параметром }Понятно, что это не "постоянно нужно так делать" (выносить функции в отдельный скетч). Это просто один из "приемов" в процессе написания отладки.
мы не можем вписать в функцию, так как в данном случае мы определяем первоначально состояние кнопки при запуске "железа"
и к тому же в конце функции у нас:
теперь действительно понимаю значимость функции.. в loop теперь на много проще оперировать ими..
#define GPRS Serial #define BTN_PIN 2 boolean prevBtn = LOW; boolean currBtn; char NUM_TEL[]="+78*************"; void setup(){ initGPRS(); //вызов функции инициализации GPRS } void loop() { onChangedButtonSendSms(); // вызов Функции проверки изменения кнопки } void sendTextMessage(char* sms) { // Функция отправки SMS gprsCmdAndWait("AT+CMGS=\""); gprsCmdAndWait(NUM_TEL); gprsCmdAndWait("\"\n"); gprsCmdAndWait(sms); gprsCmdAndWait("\n"); } void gprsCmdAndWait(char* commandText){ //Функция отправки текста в Serial GPRS.print(commandText); delay(300); } void initGPRS(){ // функция инициализации GPRS GPRS.begin(9600); gprsCmdAndWait("AT+CMGF=1\n"); //выбор режима sms: 1-текстовый,0-цифровой) gprsCmdAndWait("AT+IFC=1, 1\n");//data flow controll(1, Программный режим (XON/XOFF)). gprsCmdAndWait("AT+CPBS=\"SM\"\n");//выбрать как основную память сим-карту. Данный режим стоит по умолчанию. gprsCmdAndWait("AT+CNMI=1,2,2,1,0\n"); //информирование о новом смс.. одним словом "хрень" - пока не трогаю.. }может где то и "натупил" - поправьте..
Да не... "тупостью" это не назовешь. Обычный процесс разбирательства.
Ну во первых... а где же здоровая лень? Если вас не устраивало, что gprsCmdAndWait всегда посылало в конце переводы строки, а вам хочется "когда посылать, а когда и нет". То.. можно было либо ввести ей второй параметр, который будет указыать нужно ли переводит строку, либо сделать две функции
gprsCmdAndWait(char* sms){ GPRS.print(sms); delay(100); } gprsCmdAndWaitln(char* sms){ // то же самое, только с переводом строки GPRS.println(sms) delay(100: }И пользоватся ими "по необходимости". Возможно так было-бы лучше, чем во всех строках добалять \n ... хотя... две похожие функции - тоже не симпатично (но что-то не приходит в голову как тут улучшить). Вообщем считайте это не "так было лучше", а "можно было еще и так" :)
К тому же.... я вот говорил вам, что "отделите пустой строкой в коде" строки
GPRS.print("AT+CMGS=\""); GPRS.print(NUM_TEL); GPRS.println("\""); delay(100);Что-бы было видно, что это формирование "одной строки" команды. А не "несколько команд". Тогда бы вы увидели, что нам не нужно "сворачивать" все в использование gprsCmdAndWait() . Так как это даст нам лишние задержки посреди процесса отправки команды. Зачем? Воспринимайте что gprsCmdAndWait() - это "завершение команды и ожидание ее выполнения". Тогда станет ясно, что с помощью нею, можно было заменять (что-бы логика не поменялась) только последние две строки.
GPRS.print("AT+CMGS=\""); GPRS.print(NUM_TEL); gprsCmdAndWait("\"");В этом случае у нас не поменялась логика по сравнению с оригинальной. Не пришлось переделывать саму gprsCmdAndWait (в ней так и остался GPRS.println), пожелание "все команды в итоге проходят через gprsCmdAndWait() - тоже выполнилось. Мы просто... часть команды подалить "чуть раньше", помогли gprsCmdAndWait(). Но, в итоге - все равно сделали ее вызов.
Но вы "зацепили", по сравнению с оригинальной логикой. Еще одну важную вещь (я так думаю, точно не скажу так как не знаю что у вас за шилд и какие там команды). В оригинале признаком окончания текста sms-ски была посылка символа с десятичным кодом 26. А теперь, в конце sms-ски вы послаете \n. Опасаюсь что шилд не поймет этого. Подумает что просто вы хотите написать вторую строку sms-ски :(
А что же делать, для 26 - нет специально \БУКВА, но есть выход :) Если нам в строке нужно использовать символ с каким-то кодом и код мы знаем, то мы можем написать gprsCmdAndWait("\xNN"); где NN - код символа в шестнадцатеричном виде. Возмем к примеру символ новой строки \n и представим себе что у него нет специальной буквы :) Не страшно, заглядывам в http://www.asciitable.com/ и видим что его десятичный код 10 (то есть можно было написать char(10) и это было-бы то же самое) или раз нам нужно в строке использовать, то смотрим во вторую колонку берем шестнадцатеричное значение и пишем "\x0A";
Так что... нам нужно взять 26, перевести его в шестнадцатеричное значение и вставить с помощью \x... Можно через ссылку-табличку выше, а можно.... Взять стандартный калькулятор виндовса :) В нем нажать ALT-3 (или поййти в меню View/Programmer) - переключить его в програмерский режим.
После чере переключаемся в Dec, набираем наше число 26, переключаемся в Hex - и видим его 16-тиричное значение.
Кстати ниже - есть еще вариант Bin - это если нам вдруг захотелось в двоичном виде его увидеть.
А есть еще и третий вариант :) Вдруг нет у нас калькулятора :) Но есть ардуина :) А про функцию print, мы уже читали внимательно. Тогда:
void setup(){ Serial.begin(9600); byte num=26; Serial.print("Dec=");Serial.println(num,DEC); Serial.print("Hex=");Serial.println(num,HEX); Serial.print("Bin=");Serial.println(num,BIN); } void loop(){}И смотрим в Serial Monitor Ж)