Подключение Arduino к отечественному проекту Народного мониторинга

Ещё один сервис Web-мониторинга температуры

Хочу рассказать ещё об одном сервисе для удалённого мониторинга температуры, преимущественно за окном. На этот раз проект отечественный, что не может не радовать.

view1

Внимание! Существует вторая версия данного устройства, доступная по этой ссылке: Метеостанция 2.0

Последнее обновление: 16 июля 2014 (v1.3)
Исправлена ошибка с дробными показаниями температуры

6 октября 2013 (v1.2)
При наличии нескольких термодатчиков данные передаются на сервер одновременно со всех, а не по очереди, как было раньше.

25 февраля 2013 (v1.1)
Исправлена ошибка с округлением отрицательной температуры до десятых долей градуса.


Немного о проекте
Сервис располагается по адресу http://narodmon.ru

Проект начался с обсуждения темы «Народный мониторинг температуры (vs прогноз) в различных городах. Нужен ли?» на Хабрахабре. На тот момент целью проекта являлось лишь информирование пользователей в Интернет о реальной погоде в данный момент времени в определенном месте, где есть участники данного проекта. C дальнейшим ростом аудитории разработчиков электронных устройств для работы с данным проектом, а также пополнением базы пользователей, возникла необходимость расширить рамки проекта с целью передачи сбора показаний с датчиков различного типа с более универсальным протоколом с крайне низкими требованиями к каналу передачи данных вплоть до GPRS/EDGE/3G/UMTS.

Собираем устройство
Нам понадобятся следующие компоненты:

  • Микроконтроллер Arduino или Freeduino (с ATmega328);
  • Ethernet-shield;
  • Цифровой термодатчик DS18B20;
  • Резистор 4.7 кОм;

Freeduino Through-Hole в собранном виде
Ethernet Shield
DS18B20

Все компоненты соединяются между собой также как и в предыдущей статье: Arduino: цифровой термометр с web-мониторингом. Немного повторюсь:
Термодатчик подключается по шине 1-wire по схеме с паразитным питанием. При этом, можно использовать несколько таких датчиков (все они соединяются параллельно двумя проводами) и считывать с каждого отдельную температуру. Подключать можно к любому цифровому входу микроконтроллера, кроме 10, 11, 12 и 13, которые используются для нужд Ethernet Shield.

temperature-1-wire_bb

Прошивка

Теперь заливаем в Arduino следующий скетч:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Скетч для Arduino для отправки данных о температуре на Народный мониторинг.
// Версия 1.3 (16.07.2014)
//
// Автор: Гладышев Дмитрий (2012-2014)
// http://student-proger.ru/2012/06/podklyuchenie-arduino-k-otechestvennomu-proektu-narodnogo-monitoringa/
 
#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
 
bool Debug = false; //режим отладки
 
//****************************************************************************************
byte mac[] = {  0xDE, 0xAD, 0xBE, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino
OneWire ds(2); //пин подключения термодатчика
const unsigned long postingInterval = 600000;  // интервал между отправками данных в миллисекундах (10 минут)
//****************************************************************************************
 
char server[] = "narodmon.ru";
 
EthernetClient client;
 
unsigned long lastConnectionTime = 0;           // время последней передачи данных
boolean lastConnected = false;                  // состояние подключения
 
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
 
char replyBuffer[160];
 
int CountSensors;
 
void setup() {
 
    if (Debug)
    {
      Serial.begin(9600);
    }
 
    delay(1000);
    // Ethernet connection:
    if (Ethernet.begin(mac) == 0) 
    {
      if (Debug)
      {
        Serial.println("Failed to configure Ethernet using DHCP");
      }
      // ничего не делаем
      for(;;);
    }
    // секунда для инициализации Ethernet
    delay(1000);
 
    //Узнаём количество термодатчиков
    CountSensors = DsCount();
    if (Debug)
    {
      Serial.print("Found ");
      Serial.print(CountSensors);
      Serial.println(" sensors."); 
    }
    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=");
 
    //Конвертируем 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(replyBuffer,c1);
      strcat(replyBuffer,c2);
    }
 
    //Сбрасываем поиск датчиков (кол-во нам уже известно)
    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];
 
      itos(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");
      }
      itos(Fract,temp);
      strcat(replyBuffer,temp);
 
    }
 
    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...");
    }
    // send the HTTP POST request:
    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 itos(int n, char bufp[3]) //int to string
{
  char buf[3]={'0','0','\0'}; 
  int i = 1;
 
  while (n > 0) {
    buf[i] = (n % 10)+48;
    i--;
    n /= 10;
  }
 
  for (i=0; i<3; i++)
    bufp[i]=buf[i];
}

Обратите внимание на следующие строки:

13
14
15
16
17
//****************************************************************************************
byte mac[] = {  0xDE, 0xAD, 0xBE, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino
OneWire ds(2); //пин подключения термодатчика
const unsigned long postingInterval = 600000;  // интервал между отправками данных в миллисекундах (10 минут)
//****************************************************************************************

Здесь необходимо будет придумать MAC-адрес для Arduino (рекомендую взять для этого MAC-адрес вашего компьютера), указать номер входа, к которому подключен термодатчик, а также частоту обновления информации. По MAC-адресу устройство будет идентифицироваться в проекте, поэтому он должен быть уникальным.

Теперь регистрация на сайте

Заходим на сайт, жмём кнопочку «Вход», затем «Я тут впервые». Вводим свой E-Mail и ждём пока на почту свалится письмо с паролем.

view2

Входим в систему.
view3

Теперь нажимаем кнопку «Мои датчики», затем «Добавить». Вводим MAC-адрес, который указан в прошивке. Теперь система должна отобразить подключенные термодатчики и температуру на них. Можно указать местоположение термометра на карте и дать подписи датчикам.

view4

view5

Щёлкнув по пузырьку с температурой на карте, можно просмотреть дополнительную информацию:

view6

Также можно просмотреть график изменения параметра:
view7

и отчёт о переданных числовых данных:
view8

Вот ссылка на мой датчик температуры, который исправно показывает температуру за моим окном: http://narodmon.ru/?id=170

Файлы
Скетч v1.3 (7 KB)
Скетч v1.2 (7.11 KB)
Скетч v1.1 (6.05 KB)
Библиотеки:
Ethernet (55.82 KB)
OneWire (13.84 KB)
SPI (7.45 KB)

Работа проверялась с Arduino IDE v1.0.1

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

Похожие записи:

93 Комментарии “Подключение Arduino к отечественному проекту Народного мониторинга

  1. Добрый день!
    Вроде все не сложно, но опыта работы в данной области нет.
    Вопрос: а можно ли Вас попросить изготовить похожее устройство с требуемыми мне характеристиками, что бы им пожно было пользоваться «из коробки»?

    1. Здравствуйте! К сожалению у меня сейчас не очень много свободного времени чтобы заниматься этим… Там в принципе ничего сложного. Пайки минимум — только датчик и резистор припаять к проводам. Остальные модули — как конструктор (если покупать компоненты в собранном виде). Попробуйте) Если будут вопросы — помогу чем смогу.

  2. не подскажите в чем проблема при тесте работы ардуино с датчиком:

    Данные получены Thu, 12 Sep 2013 20:44:52 +0400 с IP XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXМосковская область, Россия)

    GET /favicon.ico HTTP/1.1\r\n
    Host: map.net13.info:8283\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0\r\n
    Accept: image/png,image/*;q=0.8,*/*;q=0.5\r\n
    Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3\r\n
    Accept-Encoding: gzip, deflate\r\n
    Connection: keep-alive\r\n
    Cookie: PHPSESSID=XXXXXXXXXXXXXXXXX; lang=RU; hash=XXXXXXXXXXXXXXXXXXXXXX; id=856

    Последняя запись в СУБД Thu, 12 Sep 2013 20:44:52 +0400

    Ошибки в формате данных(если они есть):
    Первая строка должна содержать MAC устр-ва из 12-24 знаков hex(допускаются разделители ‘-‘ и ‘:’)
    Каждая строка должна начинаться с символа ‘#’
    Пакет данных должен оканчиваться строкой ‘##’

    Суть проблемы заключается в том, что не добавляется датчик температуры к систему. Будтобы ардуино с данным маком ничего не присылало

    1. Скорость порта 9600? Первая отправка данных должна быть через 15 секунд после запуска. Если вообще ничего не выводит, то проверяйте, выдаётся ли IP-адрес ардуине. Это можно посмотреть в настройках вашего роутера. Хотя в этом случае должна была вылезти ошибка «Failed to configure Ethernet using DHCP»

    1. Оно и не должно срабатывать в обычных условиях. Это на тот случай, если на саму ардуину будут поступать данные по сети, чтобы очистить буфер приёма.

  3. Скеч выше проверялся с Arduino IDE v1.5.1 ???? какие изменения в коде необходимо сделать, чтобы скеч работал в версии 1.5.1????

    1. Попробуйте в разные участки кода поставить строки
      Serial.println(«text»);
      (с разным текстом конечно). Debug установить в true. И отследить, где происходит сбой.

  4. lastConnectionTime изначаьлно получается очень огромным, может потому что lastConnectionTime = millis()-postingInterval+15000 millis() в этом выражении при выполнении скетча после прошивки ардуино очень маленькое и время lastConnectionTime получается отрицательное.за время наладки пару раз ip.php показало что данные получены на сервере.

    1. Всё правильно. Время получается отрицательным, происходит переполнение — в результате огромное число. У меня работает это нормально. Попробуйте изменить строчку
      unsigned long lastConnectionTime = 0;
      на
      signed long lastConnectionTime = 0;
      То есть явно укажем, что число может быть отрицательным. Сейчас у себя проверил — конструкция работает.

  5. Если вытащить Serial.begin(9600) из кода скетча из выражения:
    if (Debug)
    {
    Serial.begin(9600);
    }
    то serial.println(«.. ……..») везде в скетче показывает текущие данные в мониторе порта. В мониторе порта как раз заметил значение lastConnectionTime огромным порядка 10 знаков

  6. Все прекрасно работает, спасибо, а можно ли как то добавить в скетч еще поддержку датчика влажности DHT-11 или DHT-22 ? было бы вообще шикарно, я бы с удовольствием его добавил, у самого не получается написать работоспособный скетч 🙁

  7. Доброе время суток!!!
    приобрел барометр BMP0085 и датчик влажности DHT-11
    проверил на Arduino все работает
    но вот встроить в скетч для narodmon не получается , не хватает знаний

  8. c UNO работет скетч , а на arduino mega в момент загрузки скетча зависает, и незагружается тем самым до конца! Подскажите, в чем может быть проблема??? Что надо изменить в скетче?

    1. Дело скорее всего в том, что мере не хватает питания от USB. Запитайте её отдельно и все получится с большой долей вероятности.

    2. Проверил на меге, работает. Не с этого сайта правда, брал с ардуино . Ошибка библиотеки при компилияции происходила. В итоге заливка прошла через старую 15 версию. Там все переварилось .

  9. парни
    помогите добавить в скетч
    представлен выше
    два датчика
    DHT-11, DHT-22
    BMP0085

    в датчиках не использовать температурные датчики (каломбурчик)

  10. Добрый вечер, Дмитрий!
    Строка 74:
    if (!client.connected() && lastConnected)

    client.stop();
    Объясните, пожалуйста, смысл этого явления…

    1. Добрый вечер, Игорь!
      Переменная lastConnected хранит состояние подключения на предыдущей итерации. Здесь выполняется проверка условия «ЕСЛИ в данный момент клиент не подключен, но был подключен на предыдущем цикле работы», ТО выполняем код. По сути — код выполняется сразу же после закрытия соединения.

      1. Да, я понимаю. Но в условии выполняется команда «client.stop();» — а клиент и так не подключен? Тогда в чем смысл?

          1. Я почему так код смотрю… Сделал на Вашем примере код для отправке данных по часам RTC с записью на SD карту. Все работает. НО ровно три раза. После этого в http запросе пишет что нет свободных сокетов. Причем дальше температуру замеряет, на SD пишет и часы по NTP через интернет проверяет…
            Может взглянете: http://goo.gl/FRd631

  11. Уважаемый , подскажи плз :заказал w5100 и DS18B20 с али , на даннный момент есть в наличии uno , двухстрочный lcd1602 и lm35 . Хотелось бы что бы результат с уличного , не только отсылался на народмон , но и выводился на lcd (допустим первая строка на lcd это наружная температура и во второй строке температура комнатная с lm35). Посему вопрос , как реализовать , что в существующем скетче переделать и в каком месте вставить обращение к lcd . Просьба не пиннать сильно — нуб в программировании .Заранее благодарен .

    1. Вставить вывод на LCD можно после 195 строки. Единственное что нужно сделать, завести отдельную переменную для хранения числового значения температуры, которую затем и отсылать через lcd.print()
      На днях постараюсь написать такой код.

  12. Здравствуйте. Подскажите рабочий скетч для atmega 168 pa-pu. У меня этот не помещается 🙁
    Binary sketch size: 14790 bytes (of a 14336 byte maximum)

  13. Добрый день, объясните пожалуйста — адрес сервера 94.19.113.221 — он что делает. Там картинка заглушка с котом стоит.

      1. Спасибо за быстрый ответ. Заказал сейчас блоки с ebay, потом буду пробовать собрать все. Также присоединяюсь к просьбам, которые были ранее — Вы добавите поддержку датчиков влажности и давления? Было бы здорово!

  14. Все собрал, запустил, работает, ура! А добавка датчиков давления и влажности будет? Или все остановилось уже?

    1. Ну не совсем остановилось, скажем так ПРИостановилось) Сейчас времени не хватает, в связи с написанием дипломной. Но думаю, что скоро я вновь возьмусь за электронику и этот сайт.

      1. Приветствую! Респект и уважуха Вам! Как и обещано было — в июле. Сейчас на отдыхе, в понедельник буду пробывать подключать — благо датчики в наличии. Как экзамены сдали?

    1. Здравствуйте. Добавьте в начале кода (до 33 строки) строчку:

      byte ip[] = { 192, 168, 1, 177 };

      Адрес конечно поставьте свой.
      Далее замените строку 42:

      42
      
      if (Ethernet.begin(mac) == 0)

      на

      42
      
      if (Ethernet.begin(mac,ip) == 0)

      Это всё)

    2. Кстати, вполне возможно, что понадобится также указать DNS и шлюз. Тогда строчка будет выглядеть так:

      if (Ethernet.begin(mac, ip, dns, gateway) == 0)
    1. У меня сейчас всё это собрано просто на МК ATmega328. В интернете можно найти обвязку ардуины, просто повторяем минимальную схему и всё. С Ethernet-shield сложнее.

  15. Запускается только после нажатия Reset.. Те. после отключения питания отваливается Ethernet до нажатия на reset

  16. Возможно ли добавить в ваш код. код с ИК пульта. что бы при нажатии кнопке на пульте. включать реле?
    #include
    int RECV_PIN = 7;
    IRrecv irrecv(RECV_PIN); //Создаем объект получения сигнала с определнного порта
    decode_results results; //Переменная, хранящая результат
    int r = 0; // переменная переключения режима, при нажатии кнопки она принимает значение +1
    void setup()
    {

    irrecv.enableIRIn(); // Начинаем прием
    irrecv.enableIRIn(); // включить приемник
    pinMode(12, OUTPUT);

    }

    void loop() {

    if (irrecv.decode(&results)) //При получении сигнала…
    {

    irrecv.resume(); // Получаем следующее значение
    }
    delay(50); //пауза между повторами //
    if (irrecv.decode(&results)) {
    delay(300); // задержка перед выполнением определения кнопок, чтобы избежать быстрое двойное нажатие
    if (results.value == 0x9C455CBF) {r = r + 1;}

    // начинаем включать светодиоды на пинах
    if (r == 1) {digitalWrite(12, HIGH);}
    else {digitalWrite(12, LOW);r = 0;}

    // {
    // delay(50); //пауза между повторами //
    // } //
    irrecv.resume(); //
    //delay(20);

    }
    }

    1. Здравствуйте. Сам по себе от внешней среды он практически не изолирован. Всё находится в небольшой плоской пластиковой коробочке, которая имеет отверстия для вентиляции. Все контакты для защиты от воды я покрыл лаком.

  17. Отличная статья, скетч хорошо подходит для экспериментов. К имеющемуся в коде датчику температуры DS1820 от себя добавил датчик давления BMP180, датчик влажности DHT22 и релейный ( в смысле да/нет) датчик движения Colt-XS. 🙂
    Все это работает на Arduino Uno. Если кому интересно, вот ссылка https://yadi.sk/d/L8_1__jQpRzSP
    Как это работает, видно здесь: http://narodmon.ru/8407

    1. Датчики влажности и давления в коде уже давно есть) Смотрите последнюю версию статьи. Скоро туда же добавится счётчик Гейгера и ещё кучка разных датчиков (освещённости, задымления и т.д.)

  18. «Народный мониторинг» недавно сменил хостинг. С этим событием сменился IP сервера, а он фигурирует в скетче. Данные больше не доходят с подобных устройств. Как бы грамотно и лаконично поменять скетч для перезаливки, чтобы опять заработало?

    1. Здравствуйте! Необходимо заменить в скетче строку

      19
      
      IPAddress server(94,19,113,221); // IP сервера

      на

      19
      
      char server[] = "narodmon.ru";

      Сейчас изменю скетчи на сайте…

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *