Удаленный контроль за температурой.

Тема в разделе "Глядите, что я сделал", создана пользователем bvv, 2 окт 2012.

  1. bvv

    bvv Нерд

    Задача: ежедневно получать данные о температуре в помещении и на улице. Поскольку помещение находится далековато, то последующая отсылка смс. Буду рад любым советам по оптимизации кода. Это мой первый проект на ардуино. Да и не программист я по жизни ни разу :)
    Собрано из:
    - непосредственно Arduino
    - два датчика температуры DHT11
    - GPRS Shield
     
  2. bvv

    bvv Нерд

    // подключаем библитотеку датчика
    #include <dht.h>
    // подключаем библиотеку GPRS модуля
    #include <SoftwareSerial.h>
    // заводим serial-соединение на 7 и 8 цифровых входах
    SoftwareSerial gprsSerial(7, 8);

    // прописываем объекты - два сенсора
    // внешний
    DHT TempOut = DHT();
    // и внутренний
    DHT TempIn = DHT();
    void setup ()
    {
    gprsSerial.begin(19200);
    //методом attach объявляем к какому контакту подключен сенсор.
    //нулевой контакт для внешнего
    TempOut.attach(A0);
    // и первый для внутреннего
    TempIn.attach(A1);
    // после подачи питания ждем 30 секунд до готовности сенсора
    delay(30000);
    // Шлем тестовое сообщение для проверки работоспособности
    // А то мало ли, антенна отвалилась...
    // Устанавливает текстовый режим для SMS-сообщений
    gprsSerial.print("AT+CMGF=1\r");
    delay(100); // даём время на усваивание команды
    // Устанавливаем адресата: телефонный номер в международном формате
    gprsSerial.println("AT + CMGS = \"+7хххххххххх\"");
    delay(100);
    // Пишем текст сообщения
    gprsSerial.println("System status OK");
    delay(100);
    // Отправляем Ctrl+Z, обозначая, что сообщение готово
    gprsSerial.println((char)26);
    }

    void loop ()
    {
    delay(28800000); // Этой задержкой устанавливаем, когда начнется работа программы
    // скажем, запускаю устройство я днем, а меня интересуют ночные температуры. 8 часов
    char MsgAll[80]; // сообщение сводное
    char msgIn[40];
    char msgOut[40];
    String msgInS = String(msgIn);
    String msgOutS = String(msgOut);
    String MsgAllS = String(MsgAll);

    SensOutRead: // метка для цикла goto
    TempOut.update(); //обновляем показания датчика
    if (TempOut.getLastError() == DHT_ERROR_OK)
    // Проверяем на наличие ошибок при считывании с датчика.
    {
    // Если ошибок нет, снимаем показания и переходим к внутреннему датчику
    sprintf(msgOut, "Temp.Out = %dC,Hum.Out = %d%%; ",
    TempOut.getTemperatureInt(), TempOut.getHumidityInt());
    goto SensInRead;
    }
    // Если есть ошибки, то считываем заново.
    else;
    {
    delay(1000); // задержка необходима, иначе шлет постоянно ошибки
    // 100 миллисекунд мало - проверял
    goto SensOutRead;
    }

    SensInRead:
    TempIn.update();
    if (TempIn.getLastError() == DHT_ERROR_OK)
    {
    sprintf(msgIn, "Temp.In = %dC,Hum.In = %d%%",
    TempIn.getTemperatureInt(), TempIn.getHumidityInt());
    goto OutpData;
    }
    else;
    {
    delay(1000);
    goto SensInRead;
    }

    OutpData:

    MsgAllS = msgOutS + msgInS; // "склеиваем одно большое
    // сообщение из двух маленьких
    delay(28800000); // Устанавливаю, когда слать смс-ку.
    //Мало счастья среди ночи подрываться к мобильнику и смотреть:
    //"А сколько у меня там градусов ниже нуля?" 8 часов
    // Устанавливает текстовый режим для SMS-сообщений
    gprsSerial.print("AT+CMGF=1\r");
    delay(100); // даём время на усваивание команды
    // Устанавливаем адресата: телефонный номер в международном формате
    gprsSerial.println("AT + CMGS = \"+7хххххххххх\"");
    delay(100);
    // Пишем текст сообщения
    gprsSerial.println(MsgAllS);
    delay(100);
    // Отправляем Ctrl+Z, обозначая, что сообщение готово
    gprsSerial.println((char)26);

    delay(28800000); //8 часов
    }
     
  3. nailxx

    nailxx Официальный Нерд Администратор

    Круть!

    По коду: предлагаю избавиться от `goto` и инкапсулировать чтение сенсора в функцию:

    Код (Text):

    // подключаем библитотеку датчика
    #include <dht.h>
    // подключаем библиотеку GPRS модуля
    #include <SoftwareSerial.h>
    // заводим serial-соединение на 7 и 8 цифровых входах
    SoftwareSerial gprsSerial(7, 8);
     
    // прописываем объекты - два сенсора
    // внешний
    DHT TempOut = DHT();
    // и внутренний
    DHT TempIn = DHT();
    void setup ()
    {
        gprsSerial.begin(19200);
        //методом attach объявляем к какому контакту подключен сенсор.
        //нулевой контакт для внешнего
        TempOut.attach(A0);
        // и первый для внутреннего
        TempIn.attach(A1);
        // после подачи питания ждем 30 секунд до готовности сенсора
        delay(30000);
        // Шлем тестовое сообщение для проверки работоспособности
        // А то мало ли, антенна отвалилась...
        // Устанавливает текстовый режим для SMS-сообщений
        gprsSerial.print("AT+CMGF=1\r");
        delay(100); // даём время на усваивание команды
        // Устанавливаем адресата: телефонный номер в международном формате
        gprsSerial.println("AT + CMGS = \"+7хххххххххх\"");
        delay(100);
        // Пишем текст сообщения
        gprsSerial.println("System status OK");
        delay(100);
        // Отправляем Ctrl+Z, обозначая, что сообщение готово
        gprsSerial.println((char)26);
    }
     
    void readDHT(DHT* sensor, char* out)
    {
        sensor->update(); //обновляем показания датчика
     
        while (sensor->getLastError() != DHT_ERROR_OK) {
            delay(1000); // задержка необходима, иначе шлет постоянно ошибки
                         // 100 миллисекунд мало - проверял
            sensor->update(); //обновляем показания датчика
        }
     
        sprintf(out, "Temp.Out = %dC,Hum.Out = %d%%; ",
                sensor->getTemperatureInt(), sensor->getHumidityInt());
    }
     
    void loop()
    {
        delay(28800000); // Этой задержкой устанавливаем, когда начнется работа программы
        // скажем, запускаю устройство я днем, а меня интересуют ночные температуры. 8 часов
        char MsgAll[80]; // сообщение сводное
        char msgIn[40];
        char msgOut[40];
        String msgInS = String(msgIn);
        String msgOutS = String(msgOut);
        String MsgAllS = String(MsgAll);
     
        readDHT(&TempOut, msgOut);
        readDHT(&TempIn, msgIn);
     
        MsgAllS = msgOutS + msgInS; // "склеиваем одно большое
        // сообщение из двух маленьких
        delay(28800000); // Устанавливаю, когда слать смс-ку.
        //Мало счастья среди ночи подрываться к мобильнику и смотреть:
        //"А сколько у меня там градусов ниже нуля?" 8 часов
        // Устанавливает текстовый режим для SMS-сообщений
        gprsSerial.print("AT+CMGF=1\r");
        delay(100); // даём время на усваивание команды
        // Устанавливаем адресата: телефонный номер в международном формате
        gprsSerial.println("AT + CMGS = \"+7хххххххххх\"");
        delay(100);
        // Пишем текст сообщения
        gprsSerial.println(MsgAllS);
        delay(100);
        // Отправляем Ctrl+Z, обозначая, что сообщение готово
        gprsSerial.println((char)26);
     
        delay(28800000); //8 часов
    }
     
     
  4. bvv

    bvv Нерд

    Нублин. "Сразу видно - высшее образование!" (с) :) Я б до такого не додумался ни в жисть! :) Спасибо за совет.
     
  5. bvv

    bvv Нерд

    Теперь осталось понять, чего Вы тут понаписали. Иначе неспортивно.
     
  6. nailxx

    nailxx Официальный Нерд Администратор

    Всё то же самое, что и вы, но другими словами. Это называется рефакторинг.

    Момент первый. `goto` сильно усложняет чтение программы, поэтому считается дурным тоном. Всякий `goto` можно заменить на конструкцию из if'ов и while'ов. У вас было:

    Код (Text):

     
    SensOutRead: // метка для цикла goto
        TempOut.update(); //обновляем показания датчика
        if (TempOut.getLastError() == DHT_ERROR_OK)
            // Проверяем на наличие ошибок при считывании с датчика.
        {
            // Если ошибок нет, снимаем показания и переходим к внутреннему датчику
            sprintf(msgOut, "Temp.Out = %dC,Hum.Out = %d%%; ",
                    TempOut.getTemperatureInt(), TempOut.getHumidityInt());
            goto SensInRead;
        }
        // Если есть ошибки, то считываем заново.
        else
        {
            delay(1000); // задержка необходима, иначе шлет постоянно ошибки
            // 100 миллисекунд мало - проверял
            goto SensOutRead;
        }
     
     
    SensInRead:
        // ...
     
     
    Этот код для чтения одного сенсора залезает в «чужой» код для другого сенсора посредством метки и если захочется поменять блоки местами, что-то подкрутить, придётся задумываться как они будут стыковаться. Думать сложно, поэтому код можно переписать, чтобы он был отдельным понятным блоком. Вы ведь имели в виду: читаем пока не вычитаем и затем пихаем всё в строку? Так и пишем:
    Код (Text):

        TempOut.update(); //обновляем показания датчика
     
        while (TempOut.getLastError() != DHT_ERROR_OK) {
            delay(1000); // задержка необходима, иначе шлет постоянно ошибки
                         // 100 миллисекунд мало - проверял
            TempOut.update(); //обновляем показания датчика
        }
     
        sprintf(out, "Temp.Out = %dC,Hum.Out = %d%%; ",
                TempOut.getTemperatureInt(), TempOut.getHumidityInt());
     
    Далее, вы читаете показания с двух датчиков. Делаете это одинаковым образом. Разница лишь в экземпляре датчика и строке в которую вы кладёте результат. Что если датчиков станет 3? Или 33? Придётся копипастить код, вытягивая его в большущий рулон. А что если затем нужно будет поменять формат строки с показаниями? Придётся 33 раза делать изменения.
    Это называется нарушением принципа DRY (Don't Repeat Yourself). Чтобы всё исправить достаточно поместить повторяющийся код в функцию. Что мы и делаем:
    Код (Text):

    void readDHT(DHT* sensor, char* out)
    {
        sensor->update(); //обновляем показания датчика
     
        while (sensor->getLastError() != DHT_ERROR_OK) {
            delay(1000); // задержка необходима, иначе шлет постоянно ошибки
                         // 100 миллисекунд мало - проверял
            sensor->update(); //обновляем показания датчика
        }
     
        sprintf(out, "Temp.Out = %dC,Hum.Out = %d%%; ",
                sensor->getTemperatureInt(), sensor->getHumidityInt());
    }
     
    Эта функция считывает показания с какого-то DHT-датчика и кладёт результат в какую-то строку. О каких именно датчиках и строках идёт речь, мы определяем при вызове функции:
    Код (Text):

    [FONT=Consolas]    readDHT(&TempOut, msgOut);
        readDHT(&TempIn, msgIn);[/FONT]
     
    Вопросы могут вызвать символы * и &. Дело в том, что в функцию нам следует передавать указатель на датчик, а не сам датчик. Так мы говорим, что хотим работать с чем-то уже существующим, просто по псевдониму. В данном случае псевдоним называется `sensor`. & перед именем переменной означает «получить псевдоним». -> после `sensor`, означает то же, что и точка в обычном случае, но для указателей нужно использовать ->. Так уж повелось, такие правила языка C++
     
  7. bvv

    bvv Нерд

    "Всю ночь перечитывал пейджер. Много думал..."
    Спасибо за внимание :) Обещаю найти время и во всем разобраться :) Только, чур, потом не обижаться на идиотские вопросы :)
    Кстати, а не заглянете на мой пост про GPRS Shield... Может можно и без этих бесконечных делэев обойтись, если часики то запустить... Дурацкая привычка: если что-то можно улучшить, то его надо улучшить.
     
  8. bvv

    bvv Нерд

    Какая-то странная фигня.... Проект то выложил. Протестировал на коротких задержках порядка 30000 мс. Все работало. А теперь запустил с большими - нет смс-ки. Запустил прибор около 8 вечера. Ближе к 12 дня рассчитывал получить сообщение. Нету. Почему? Ноликов лишних написал? Или ардуина таких больших чисел боится?
    Помогайте, гуру, великие и ужасные :)
     
  9. bvv

    bvv Нерд

    Теоретически вроде понял все. Надо самому какую-нибудь функцию написать, чтоб убедиться наверняка. Что понял :) Спасибо.
     
  10. nailxx

    nailxx Официальный Нерд Администратор

    Да. Максимальное значение int — 32767 (2^15 - 1). Поэтому для больших задержек вам нужно использовать тип long. У него максимум более 2 миллиардов. Но функция delay принимает int. Поэтому, храните в `long` переменной сколько ещё осталось поспать, а в цикле спите, скажем, по секунде и уменьшайте значение того сколько осталось поспать на секунду после каждого пробуждения.
     
  11. bvv

    bvv Нерд

    Ясно. Спасибо. Попробую с часиками поэкспериментировать. Оно всяк красивее будет.
     
  12. bvv

    bvv Нерд

    В итоге получилось как-то так. Будет похолоднее, поеду ставить прибор на "боевое дежурство" :)

    Код (Text):
    // подключаем библитотеку датчика
    #include <dht.h>
    // подключаем библиотеку GPRS модуля
    #include <SoftwareSerial.h>
    // заводим serial-соединение на 7 и 8 цифровых входах
    SoftwareSerial gprsSerial(7, 8);
     
    // прописываем объекты - два сенсора
    // внешний
    DHT TempOut = DHT();
    // и внутренний
    DHT TempIn = DHT();
     
    void setup ()
    {
      gprsSerial.begin(19200);
      //методом attach объявляем к какому контакту подключен сенсор.
      //нулевой контакт для внешнего
      TempOut.attach(A0);
      // и первый для внутреннего
      TempIn.attach(A1);
      // после подачи питания ждем 30 секунд до готовности сенсора
      delay(30000);
          // Шлем тестовое сообщение для проверки работоспособности
          // А то мало ли, антенна отвалилась...
          // Устанавливает текстовый режим для SMS-сообщений
        gprsSerial.print("AT+CMGF=1\r");
        delay(100); // даём время на усваивание команды
        // Устанавливаем адресата: телефонный номер в международном формате
        gprsSerial.println("AT + CMGS = \"+79857690401\"");
        delay(100);
        // Пишем текст сообщения
        gprsSerial.println("System status OK");
        //Serial.println("System status OK");
        delay(100);
        // Отправляем Ctrl+Z, обозначая, что сообщение готово
        gprsSerial.println((char)26);
     
    }
     
    void Sleep(int H) //функция для установки длительной задержки. параметр H - время в часах,
    {
      long Sleep = (H * 3600000);
      int Start;
      while (Sleep > 32000)
      {
        Start = 32000;
        delay(Start);
        Sleep = Sleep - Start;
      }
        Start = Sleep;
        delay(Start);
    }
     
    void readDHT(DHT* sensor, char* MsgTemp) //функция считаывния показаний
    {
        sensor->update(); //обновляем показания датчика
        while (sensor->getLastError() != DHT_ERROR_OK)
        {
            delay(1000); // задержка необходима, иначе шлет постоянно ошибки
                        // 100 миллисекунд мало - проверял
            sensor->update(); //обновляем показания датчика
        }
                sprintf(MsgTemp, "Temp=%dC,Hum=%d%%;",
                sensor->getTemperatureInt(), sensor->getHumidityInt());
    }
     
    void loop ()
    {
      char msgIn[40];
      char msgOut[40];
      String msgInS = String(msgIn);
      String msgOutS = String(msgOut);
      String MsgAllS;
     
      Sleep(11); // время от времени запуска до времени считывания. считываем около 4 утра.
     
        readDHT(&TempIn, msgIn);
        readDHT(&TempOut, msgOut);
        MsgAllS = "Out:" + msgOutS + " In:" + msgInS;
     
      Sleep(10); // Этот параметр не меняем
     
        gprsSerial.print("AT+CMGF=1\r");
        delay(100); // даём время на усваивание команды
        // Устанавливаем адресата: телефонный номер в международном формате
        gprsSerial.println("AT + CMGS = \"+79857690401\"");
        delay(100);
        // Пишем текст сообщения
        gprsSerial.println(MsgAllS);
        delay(100);
        // Отправляем Ctrl+Z, обозначая, что сообщение готово
        gprsSerial.println((char)26);
       
      Sleep(3); // Сумма всех параметров "H" трех "слипов" равна 24
    }
     
     
     
  13. Илья

    Илья Нуб

    а часы победить не удалось?
    а то мне тоже надо время, а вешать дополнительно внешние часы не хочется, когда есть уже на GPRS-модуле
     
  14. bvv

    bvv Нерд

    не, не удалось. все мучения - в моей ветки про gprs-sgield. как-то оно работало странно.