Метеостанция 2.0

Наконец-то у меня появилось свободное время. Решил заняться некоторыми давно заброшенными проектами. В частности, устройством для отправки данных о метеоситуации за окном на «Народный мониторинг» при помощи Arduino.

Информация
Доступна обновлённая версия скетча по ссылке: Метеостанция 2.1

Первая версия устройства описывалась в статье Подключение Arduino к отечественному проекту Народного мониторинга. Теперь научим его отправлять не только температуру, но и атмосферное давление и влажность. Для этого нам понадобятся следующие компоненты:

  • Микроконтроллер Arduino или Freeduino (с ATmega328);
  • Ethernet-shield Wiznet w5100;
  • Цифровой термодатчик DS18B20;
  • Датчик атмосферного давления BMP085;
  • Датчик влажности DHT22;
  • Резистор 4.7 кОм;
  • Резистор 5 кОм.

Повторю некоторые фотографии из предыдущей статьи. Микроконтроллер и Ethernet-shield:

DS18B20:

ds18b20

DHT22:

BMP085:

Соединяем всё по следующей схеме:

Резистор R1 на схеме — 4.7 кОм, служит для подтяжки линии данных для термодатчика DS18B20. Резистор R2 — 5 кОм, подтягивает линию данных DHT22. Термодатчик и датчик влажности размещаются на улице. Я для этого использовал четырёхжильный телефонный кабель — на расстоянии 6 метров от микроконтроллера работает замечательно. Датчик давления размещается в доме. За счёт негерметичности окон/дверей атмосферное давление снаружи и внутри будет практически одинаковым.

Возможно кто-то обратит внимание и задаст вопрос: почему DS18B20 подключается по трёхпроводной схеме, а в прошлой статье было всего два? Дело в том, что на то время я использовал паразитное питание термодатчика. Однако, на больших расстояниях от микроконтроллера это может давать сбои. Поэтому рекомендую принудительно подавать питание на датчик.

Если у вас нет сенсоров DHT22 или BMP085, то просто оставьте соответствующие контакты Arduino неподключенными. В коде необходимо будет подправить всего лишь одну-две строки.
Ну и вот собственно код скетча:

// Скетч для Arduino для отправки метеоданных на Народный мониторинг.
// Версия 2.0 (19.07.2014)
//
// Автор: Гладышев Дмитрий (2012-2014)
// http://student-proger.ru/2014/07/meteostanciya-2-0/


#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
#include <Wire.h>
#include <BMP085.h>
#include <DHT.h>

bool Debug = false; //режим отладки

//********************************************************************************************
byte mac[] = { 0xDE, 0xAD, 0xBE, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino
#define BMP085_EXIST 1          // наличие датчика атмосферного давления
#define DHT_EXIST 1             // наличие датчика влажности
#define DS18B20_PIN 2           // пин подключения термодатчика DS18B20
#define DHTPIN 6                // пин подключения датчика влажности DHT22
#define DHTTYPE DHT22           // тип датчика влажности DHT22/DHT11
#define postingInterval 600000  // интервал между отправками данных в миллисекундах (10 минут)
//********************************************************************************************

char server[] = "narodmon.ru";
char macbuf[13];

EthernetClient client;
OneWire ds(DS18B20_PIN);

#if BMP085_EXIST == 1
  BMP085 dps = BMP085();
#endif

#if DHT_EXIST == 1
  DHT dht(DHTPIN, DHTTYPE);
#endif

unsigned long lastConnectionTime = 0;           // время последней передачи данных
boolean lastConnected = false;                  // состояние подключения
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
char replyBuffer[160];                          // буфер для отправки
int CountSensors;                               // количество найденных датчиков температуры
long Pressure = 0;
float Humidity = 0;

void setup() {

  if (Debug)
  {
    Serial.begin(9600);
  }
  
  // секунда для инициализации Ethernet
  delay(1000);
  // Пробуем подключиться по Ethernet
  if (Ethernet.begin(mac) == 0) 
  {
    if (Debug)
    {
      Serial.println("Failed to configure Ethernet using DHCP");
    }
    // ничего не делаем
    for(;;);
  }

 //Узнаём количество термодатчиков
  CountSensors = DsCount();
  if (Debug)
  {
    Serial.print("Found ");
    Serial.print(CountSensors);
    Serial.println(" sensors."); 
  }
  
  #if BMP085_EXIST == 1
    Wire.begin();
    dps.init();
  #endif
  #if DHT_EXIST == 1
    dht.begin();
  #endif    
  
  lastConnectionTime = millis()-postingInterval+15000; //первое соединение через 15 секунд после запуска
}

void loop()
{
  //Если вдруг нам случайно приходят откуда-то какие-то данные,
  //то просто читаем их и игнорируем, чтобы очистить буфер
  if (client.available()) 
  {
    client.read();
  }

  if (!client.connected() && lastConnected) 
  {
    if (Debug)
    {
      Serial.println();
      Serial.println("disconnecting.");
    }
    client.stop();
  }

  //если не подключены и прошло определённое время, то делаем замер,
  //переподключаемся и отправляем данные
  if (!client.connected() && (millis() - lastConnectionTime > postingInterval)) 
  {

    //формирование HTTP-запроса
    memset(replyBuffer, 0, sizeof(replyBuffer));
    strcpy(replyBuffer,"ID=");

    memset(macbuf, 0, sizeof(macbuf));
    //Конвертируем MAC-адрес
    for (int k=0; k<6; k++)
    {
      int b1=mac[k]/16;
      int b2=mac[k]%16;
      char c1[2],c2[2];

      if (b1>9) c1[0]=(char)(b1-10)+'A';
      else c1[0] = (char)(b1) + '0';
      if (b2>9) c2[0]=(char)(b2-10)+'A';
      else c2[0] = (char)(b2) + '0';

      c1[1]='\0';
      c2[1]='\0';

      strcat(macbuf,c1);
      strcat(macbuf,c2);
    }
    strcat(replyBuffer, macbuf);

    //Сбрасываем поиск датчиков (кол-во нам уже известно)
    ds.reset_search();
    //Теперь в цикле опрашиваем все датчики сразу

    for (int j=0; j<CountSensors; j++)
    {

      byte i;
      byte present = 0;
      byte data[12];
      byte addr[8];

      if ( !ds.search(addr)) 
      {
        ds.reset_search();
        return;
      }

      ds.reset();
      ds.select(addr);
      ds.write(0x44,1);

      delay(1000);

      present = ds.reset();
      ds.select(addr);    
      ds.write(0xBE);

      for ( i = 0; i < 9; i++) // we need 9 bytes
      {
        data[i] = ds.read();
      }

      LowByte = data[0];
      HighByte = data[1];
      TReading = (HighByte << 8) + LowByte;
      SignBit = TReading & 0x8000;  // test most sig bit
      if (SignBit) // negative
      {
        TReading = (TReading ^ 0xffff) + 1; // 2's comp
      }
      Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

      Whole = Tc_100 / 100;  // separate off the whole and fractional portions
      Fract = Tc_100 % 100;

      char temp[3];

      itoa(Whole,temp);
      strcat(replyBuffer,"&");

      //конвертируем адрес термодатчика
      for (int k=7; k>=0; k--)
      {
        int b1=addr[k]/16;
        int b2=addr[k]%16;
        char c1[2],c2[2];

        if (b1>9) c1[0]=(char)(b1-10)+'A';
        else c1[0] = (char)(b1) + '0';
        if (b2>9) c2[0]=(char)(b2-10)+'A';
        else c2[0] = (char)(b2) + '0';

        c1[1]='\0';
        c2[1]='\0';

        strcat(replyBuffer, c1);
        strcat(replyBuffer, c2);
      }
      strcat(replyBuffer,"=");
      if (SignBit) //если температура отрицательная, добавляем знак минуса
      {
        strcat(replyBuffer,"-");
      }
      strcat(replyBuffer,temp);
      strcat(replyBuffer,".");
      if (Fract<10)
      {
        strcat(replyBuffer,"0");
      }
      itoa(Fract,temp);
      strcat(replyBuffer,temp);
    }

    char temp[8];
    long p_100, h_100;
    
    #if BMP085_EXIST == 1
      strcat(replyBuffer, "&");
      strcat(replyBuffer, macbuf);
      strcat(replyBuffer, "01=");
      dps.getPressure(&Pressure);
      p_100 = Pressure/1.333;
      Whole = p_100 / 100;
      Fract = p_100 % 100;
      itoa(Whole, temp);
      strcat(replyBuffer, temp);
      strcat(replyBuffer, ".");
      if (Fract<10)
      {
        strcat(replyBuffer,"0");
      }
      itoa(Fract, temp);
      strcat(replyBuffer, temp);
    #endif
    
    #if DHT_EXIST == 1
      Humidity = dht.readHumidity();
      strcat(replyBuffer, "&");
      strcat(replyBuffer, macbuf);
      strcat(replyBuffer, "02=");
      h_100 = Humidity*100;
      Whole = h_100 / 100;
      Fract = h_100 % 100;
      itoa(Whole, temp);
      strcat(replyBuffer, temp);
      strcat(replyBuffer, ".");
      if (Fract<10)
      {
        strcat(replyBuffer,"0");
      }
      itoa(Fract, temp);
      strcat(replyBuffer, temp);
    #endif

    strcat(replyBuffer,'\0');

    if (Debug)
    {
      Serial.println(replyBuffer);
      Serial.print("Content-Length: ");
      Serial.println(len(replyBuffer));
    }

    //отправляем запрос
    httpRequest();

  }
  //храним последнее состояние подключения
  lastConnected = client.connected();
}

void httpRequest() 
{
  if (client.connect(server, 80))
  {
    if (Debug)
    {
      Serial.println("connecting...");
    }
    // отправляем HTTP POST запрос:
    client.println("POST http://narodmon.ru/post.php HTTP/1.0");
    client.println("Host: narodmon.ru");
    //client.println("User-Agent: arduino-ethernet");
    //client.println("Connection: close");
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.print("Content-Length: ");
    client.println(len(replyBuffer));
    client.println();
    client.println(replyBuffer);
    client.println();

    lastConnectionTime = millis();
  } 
  else
  {
    if (Debug)
    {
      Serial.println("connection failed");
      Serial.println("disconnecting.");
    }
    client.stop();
  }
}

//Количество термодатчиков на шине
int DsCount()
{
  int count=0;
  bool thatsall = false;
  byte addr[8];
  do
  {
    if ( !ds.search(addr))
    {
      ds.reset_search();
      thatsall = true;
    }
    count++;
  } while(!thatsall);
  return (count-1);
}

int len(char *buf)
{
  int i=0; 
  do
  {
    i++;
  } while (buf[i]!='\0');
  return i;
}

void reverse(char s[])
{
  int i, j;
  char c;
  
  for (i = 0, j = strlen(s)-1; i<j; i++, j--) 
  {
    c = s[i];
    s[i] = s[j];
    s[j] = c;
  }
}

void itoa(int n, char s[])
{
  int i, sign;
  
  if ((sign = n) < 0)       /* записываем знак */
    n = -n;                 /* делаем n положительным числом */
  i = 0;
  do {                      /* генерируем цифры в обратном порядке */
    s[i++] = n % 10 + '0';  /* берем следующую цифру */
  } while ((n /= 10) > 0);  /* удаляем */
  if (sign < 0)
    s[i++] = '-';
  s[i] = '\0';
  reverse(s);
}

Обратите внимание на строки 17-23:

  • mac — MAC-адрес Arduino. Это ваш уникальный идентификатор на сайте народного мониторинга. Поэтому для защиты от совпадений рекомендую использовать MAC-адрес вашего компьютера/роутера/телефона с изменённым последним байтом (чтобы не было коллизии внутри локальной сети).
  • BMP085_EXIST — наличие датчика атмосферного давления. Поставьте 0, если у вас его нет.
  • DHT_EXIST — наличие датчика влажности. Поставьте 0, если у вас его нет.
  • DS18B20_PIN — пин подключения термодатчика DS18B20.
  • DHTPIN — пин подключения датчика влажности DHT22.
  • DHTTYPE — тип датчика влажности: DHT22 или DHT11
  • postingInterval — интервал передачи показаний. По-умолчанию 600000 мс (10 минут).

Если при компиляции в Arduino 1.0.5 вы получаете следующие ошибки:

ArduinoRobot.cpp : : In constructor 'RobotControl::RobotControl()':
ArduinoRobot.cpp : 'LCD_CS' was not declared in this scope
ArduinoRobot.cpp : 'DC_LCD' was not declared in this scope
ArduinoRobot.cpp : 'RST_LCD' was not declared in this scope
ArduinoRobot.cpp : : In member function 'void RobotControl::begin()':
ArduinoRobot.cpp : 'MUXA' was not declared in this scope
ArduinoRobot.cpp : 'MUXB' was not declared in this scope
ArduinoRobot.cpp : 'MUXC' was not declared in this scope
ArduinoRobot.cpp : 'MUXD' was not declared in this scope
ArduinoRobot.cpp : 'MUX_IN' was not declared in this scope
ArduinoRobot.cpp : 'BUZZ' was not declared in this scope
ArduinoRobot.cpp : 'Serial1' was not declared in this scope

то удалите папку arduino-1.0.5-r2\libraries\Robot_Control.

В комментариях жду вопросов и предложений. Багрепорты также приветствуются.

Файлы

Скетч v2.0 (9.31 KB)

Библиотеки:
OneWire (13.84 KB)
BMP085 (59.14 KB)
DHT (3.37 KB)

Работа проверялась с Arduino IDE v1.0.5-r2.
Библиотеки являются собственностью их авторов!

Предупреждение!
Автор не несёт ответственности за возможную порчу оборудования. Всё, что вы делаете — вы делаете на свой страх и риск!