→ Народный мониторинг. Метеостанция на NodeMcu. Народный мониторинг Получение данных отправленных из «Монитора порта» в Arduino

Народный мониторинг. Метеостанция на NodeMcu. Народный мониторинг Получение данных отправленных из «Монитора порта» в Arduino

У нас в организации развёрнут сервер Zabbix для мониторинга работоспособности серверов и АРМов. Из-за особенностей техпроцесса оборудование «размазано» по нескольким помещениям и разнесено по территории предприятия. Естественно, вместе с основными параметрами компьютеров (работает/не работает) хочется контролировать и микроклимат в серверных. При этом, как обычно, возможности весьма ограничены, и «выбить» значительные средства на сложные системы мониторинга температуры (к ним я отношу и платы управления с термодатчиками для стоечных ИБП APC) - это отдельный квест.

В основной серверной всё просто - установлена одна такая плата (закуплена давным-давно предшественником вместе с основным оборудованием), воткнут APC-шный термодатчик, заведён агент в Заббиксе, всё работает по SNMP. Скучно:) На мониторинг удалённой аппаратной оборудования нет, средств тоже - см. выше. Поэтому было решено проявить смекалку, сэкономить бюджет и заодно прокачать новый навык путём конструирования простого и дешёвого «наколенного» решения, вписывающегося, тем не менее, в существующую инфраструктуру мониторинга Zabbix.

Необходимые компоненты:

  • Основа системы - Arduino Nano V3
  • Модуль локальной сети (ethernet-shield)
  • И, собственно, цифровой датчик температуры на базе DS18B20
Общая стоимость компонентов - $10 с доставкой.

Сборка устройства не составляет труда. Сетевой модуль надевается на основную плату «бутербродом», термодатчик припаивается к его пинам. Подключение датчика: красный +5 В, чёрный - земля, жёлтый - данные; между +5V и Data припаиваем подтягивающий резистор 4,7 кОм.

Пин для данных выбирается с учётом пинов, используемых сетевым модулем (D10 – SS; D11 – MOSI; D12 – MISO; D13 – SCK; D2 – IRQ).

Грабли: в прототипе устройства столкнулся с конфликтом - данные о температуре выдавались случайным образом, «через два на третий». Причиной оказалось то, что я прицепил термодатчик на пин 2, который, как потом нашёл на просторах интернета, используется сетевым модулем для генерации прерывания при поступлении пакета. Переставил на 4-й - заработало как часы.

После сборки аппаратной части переходим к программной.

Устройство будет работать в сети и притворяться заббикс-агентом, для этого ему нужен MAC и IP-адрес. Решаем, как удобнее - жёстко зашить при программировании, генерировать MAC из адреса температурного датчика и получать IP по DHCP, и т.д. Я пошёл по простейшему пути и захардкодил оба параметра.

Протокол обмена с заббикс-сервером описан в документации . Наше устройство будет откликаться на две команды - agent.ping и env.temp (здесь оставлен простор для дальнейшего творчества, можно привязать любой из модулей расширения, доступных для ардуино - хоть датчик влажности, хоть освещённости - да что душе угодно). На все остальные команды оно будет ругаться отвечать стандартным ответом, понятным заббикс-серверу.

Для тех, кто начинает с нуля (как я) - программирование Arduino выполняется с помощью Arduino IDE , установка и настройка которой элементарны. Для работы компонентов необходимы библиотеки UIPEthernet и OneWire, которые устанавливаются и подключаются к проекту через меню Скетч - Подключить библиотеку - Управлять библиотеками…
Если у вас будут другие компоненты (например, сетевой модуль не на enc28j60, а на другом чипе) - понадобятся и другие библиотеки!

Код работы с сетевым модулем и с датчиком температуры - типовой, из интернета, с некоторыми допущениями и упрощениями.

После заливки кода в контроллер и подключения ethernet-кабеля проверяем из консоли:

$ zabbix_get -s 192.168.4.5 -k agent.ping 1 $ zabbix_get -s 192.168.4.5 -k env.temp 23.12 $ zabbix_get -s 192.168.4.5 -k bla-blah ZBX_NOTSUPPORTED
Грабли: выложенная на zabbix.com скомпилированная версия zabbix_get для Windows устарела и использует другой протокол (с заголовком ZBXD\x01 в запросе сервера). Линуксовая версия актуальна и протокол соответствует приведенному коду.

Всё работает, как и задумано. В админке заббикса создаём новый хост с выбранным IP, в нём - два ключа, Numeric (unsigned) agent.ping и Numeric (float) env.temp, наслаждаемся работой. Графики, триггеры - всё как обычно.

Питание устройства - через родной USB. Корпус - по желанию: подходящая пластиковая коробочка, термоусадка, синяя изолента.

Разрешение датчика - примерно 0.06 (точнее, 1/16) °С, точность - при погружении в таящий снег показал 0.19 °С (может, опустился бы и ниже, но снега было мало и он весь быстро растаял). Считаю, для устройства стоимостью 10 долларов и описанных целей - более чем достаточно.

Скетч

#include #include byte mac = { 0xDE, 0x05, 0xB6, 0x27, 0x39, 0x19 }; // random MAC byte ip = { 192, 168, 4, 5 }; // IP address in local network String readString = String(20); byte addr; OneWire ds(4); // DS18B20 at pin 4 EthernetServer server(10050); // Zabbix port void setup() { Ethernet.begin(mac, ip); server.begin(); ds.search(addr); } void loop() { byte data; float celsius; readString = ""; if (EthernetClient client = server.available()) { while (client.connected()) { if (client.available()) { char c = client.read(); if (c == "\n") // end of query from zabbix server { client.print("ZBXD\x01"); // response header if (readString == "agent.ping") { byte responseBytes = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, "1"}; client.write(responseBytes, 9); } else if (readString == "env.temp") { ds.reset(); ds.select(addr); ds.write(0x44); // start conversion with regular (non-parasite!) power delay(1000); ds.reset(); ds.select(addr); ds.write(0xBE); // read Scratchpad data = ds.read(); data = ds.read(); int16_t raw = (data << 8) | data; celsius = (float)raw / 16.0; byte responseBytes = {(byte) String(celsius).length(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; client.write(responseBytes, 8); client.print(celsius); } else { byte responseBytes = {0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; client.write(responseBytes, 8); client.print("ZBX_NOTSUPPORTED"); } break; } else if (readString.length() < 20) { readString = readString + c; } } } delay(10); client.stop(); } }

Это совместный проект. Ссылки на каналы, сайт и архив авторов будут в конце описания. Создаётся блочная система управления ВетроСолнечной электростанции. Имеем два дачных участка с альтернативным энергообеспечением. На одном участке есть внешнее электричество, на втором полное отсутствие такового. Участки находятся в разных регионах России. На первом участке установлен самодельный ветрогенератор мощностью 1кВт, 6 стационарных солнечных панелей, по 100Вт каждая, соединённых последовательно по две штуки на 24В и 6 самодельных панелей, соединённых также на 24 вольта, установленных на солнечном треккере. На втором участке, установленные стационарно, 4 панели по 100Вт и 2 самодельных по 60 Вт подключенных также на 24 вольтовую систему. Проект позволят подключать как 12 вольтовую так и 24 вольтовую системы без механических изменений. Система состоит из пяти самостоятельных блоков. Связь межу блоками осуществляется по радиоканалу через модули NRF24L01 (с внешней антенной).

  1. Модуль «Блок управлени» на базе ARDUINO MEGA2560 — осуществляет сбор всей информации и вывод на ЖК дисплей IIC/I2C 2004 4Х20, управление блоками, устройствами и ввод различных настроечных параметров с помощью матричной клавиатулы 4Х4. Температур, влажности, давления (датчик давления bmp180 ), дата, время (часы DS3231 ). Вся настроечная информация вводися через меню и сохраняется в EEPROM. После установки всех необходимы поправочных коэффициентов в режиме отладки эти данные по радиоканалу отправляются в свои блоки, что позволяет точно настроить все данные. (Блок продолжает развиваться).
  2. Модуль «Блок ваттметра» на базе ARDUINO NANO совместно с силовым блоком- производит замер и расчет входящих и исходящих токов, напряжений используя измерительную планку с установленными на неё датчиками тока ACS712 30А и ACS754SCU 100А, АЦП ADS1015 12 бит , резистивных делителей напряжения . Кроме того регулирует установленное в параметрах выдаваемое ветрогенератором напряжение с помощью балластной нагрузки, греет воду с защитой от закипания на базе герметичного датчика DS18B20 . Отключает инвертор при разряде аккумуляторов ниже % установленного в параметрах. Включает систему охлаждения трёхфазного диодного моста.(Датчик DS18B20 ).
  3. Модуль «Блок ветрогенератора» на базе ARDUINO NANO — Состоит из двух блоков:
  • Непосредственно «Блока ветрогенератора»- производит замер и передачу: оборотов генератора (датчик Холла), температуры обмоток генератора, температуры и влажности отсека генератора(DS18B20 и DHT22 ), определение дня и ночи (фоторезисторы ). Положение хвоста ветрогенератора. А так же, в целях безопасности, трёхлучевой светодиодный маяк (мачта ветрогенератора высотой 17м).
  • «Блок анемометра»- Замер скорости ветра (датчик Холла) указатель направления ветра (датчики Холла, расширитель цифровых портов IIC I2C MCP23017 ).

4. Модуль «WEB сервер» на базе ARDUINO MEGA 2560 и интернет шилда W5100 Ethernet — выводит на сайт всю информацию об электростанции для возможности контролировать показания удалённо.

5. Модуль «Солнечный треккер»- управление положением солнечных панелей на солнце и по времени в случае отсутствия такового, управление с помощью ИК пульта, автоматическое возвращение треккера в положение на Восток после наступления ночи. Установка треккера в положение «ветрозащита» в случае превышения скорости ветра выше установленной. Управление приводами треккера. Блок реально работает уже год. Пока нет в архиве, т.к. идет его внедрение в общую блочную схему.

Начинался проект с использованием монитора NEXTION (прекрасная вещь обязательно где нибудь его используем), но потом мы отказались от этого, в виду больших тормозов из-за обьёма передаваемой информации и решения сделать систему модульной.

Так же все ARDUINO NANO прошиваются через WiFi с использованием ESP8266-01 . Это чтобы обновлять прошивку, в случае коррекции скетча, не снимая блоки с мест монтажа. Есть пока проблема с MEGA2560, в чём причина знаем, но пока не можем решить. Но это отдельная история.

Скетчи в формате FLProg, печатные платы в формате Sprint-Layout в регулярно обновляемом

В которой описывалось создание устройства для мониторинга параметров ПК. Сразу захотелось что-то подобное. Но так как опыта программирования PIC контроллеров у меня маловато, зато имеется Arduino, решил собирать на нем.

Основа устройства – Freeduino, в качестве индикатора применен LCD (16х2) дисплей с контроллером H44780 на борту, для подачи звукового сигнала использован пьезоэлектрический капсуль, снятый с китайского мультиметра. Связь полученного устройства и ПК осуществляется посредством USB.

Фото «готового» устройства:

Вот полный список деталей для сборки:
- Arduino Uno, Freeduino и т. п.
- LCD дисплей с контроллером H44780
- Пьезоэлектрический излучатель (подойдет любой, даже тот, что используется в китайских игрушках)
- Резистор 10-30 Ом
- Подстроечный резистор 10кОм

Соединяем детали по такой схеме:

Загружаем в Arduino скетч под названием “Lcd_Ram.ino” (лежит в прикрепленном к статье архиве), подключаем к ПК через кабель USB.

На дисплее устройства должна появиться такая картинка:

Если картинка не появилась, то существует четыре причины:
1) Нет питания (поврежден USB шнур);
2) Поврежден шлейф LCD (дисплей не прошел инициализацию);
3)Неисправен Arduino;
4) Недостаточная контрастность дисплея (эта причина устраняется путем изменения сопротивления подстроечного резистора);

Данное устройство способно выводить на LCD информацию о загрузке оперативной памяти и процессора (больше не позволил размер самого LCD).

Если оперативная память загружена более чем на 70%, то устройство будет подавать звуковой сигнал, а на LCD будет выводиться такой значок:

Подача звукового сигнала может выключаться из программы на Windows. Если звук включен, то на LCD выводится иконка:

Главное окно данной программы:

Для соединения с Arduino откройте в программе вкладку «COM порт» --> “Настройка COM”, и выберите виртуальный COM порт, "принадлежащий" Arduino (если вы первый раз подключаете Arduino к ПК, то будет необходимо установить драйвера FTDI) . После соединения на дисплее должна быть похожая картина.

Когда узнал о народном мониторинге, о возможности быть частью этой системы мониторинга погоды, меня охватил азарт, и была сделана простенькая метеостанция на ардуино нано и ethernet модуле.
Передавала она на сайт температуру, давление и влажность. Работала худо бедно, иногда подвисала, необходимо было перезагружать. Потом сдох датчик влажности, и я занялся поиском альтернативного устройства. Долго поглядывал на esp8266, но знал о ней очень немного. Останавливало то, что код для нее надо было писать в незнакомой мне среде esplorer на незнакомом мне языке. Здесь уже публиковался подобного устройства, устройство работало с условно бесплатной прошивкой от Homes Smart, такое решение меня не устроило. Позднее я узнал, что esp8266 можно вполне успешно подружить с arduinoIDE. Я начал усиленно гуглить, и в результате догуглился до устраивающих меня результатов.
Надеюсь, статья окажется полезной. Думаю, ее можно расценивать как обзор-руководство по сборке.

NodeMcu это своего рода отладочная плата для esp8266 (в моем понимании). С помощью нее удобно делать прототипы устройств на макетных платах. Удобно заливать прошивку из ArduinoIDE, все происходит автоматически. Естественно, необходимо ArduinoIDE к работе с esp8266. Подготовив рабочую среду сразу набросал схему, это было не сложно.
Датчик давления и температуры bmp180 подключен по i2c к SCL(gpio05) и SDA(gpio04).
Датчик влажности и температуры dht22 подключен к gpio02
Дисплей 1602 с припаянным i2c адаптером так же посажен на scl и cda, параллельно с датчиком давления.
Питание датчиков 3.3в, питание дисплея 5в.

Фоторезистор подключен к пину А0, он управляет яркостью дисплея, чтобы в ночи дисплей не светился слишком ярко. Питанием подсветки заведует gpio14, на котором реализован программный ШИМ. После транзисторного каскада питание подсветки подходит непосредственно к пятачку А (анод) на дисплее. он находится рядом со светодиодом. Со spi адаптера дисплея необходимо снять перемычку, которая отвечает за подсветку.

Фото прототипа





Схему и печатку приложу в конце.

Сложнее было с кодом.

//ардуино 181, либы схоронил #include #include #include #include #include LiquidCrystal_I2C lcd(0x27, 16, 2); #include #include #include #include #define debug true // вывод отладочных сообщений #define postingInterval 300000 // интервал между отправками данных в миллисекундах (5 минут) #define DHTPIN 2 // dht на gpio02 // Uncomment the type of sensor in use: //#define DHTTYPE DHT11 // DHT 11 #define DHTTYPE DHT22 // DHT 22 (AM2302) //#define DHTTYPE DHT21 // DHT 21 (AM2301) // Lib instantiate DHT dht(DHTPIN, DHTTYPE); Adafruit_BMP085 bmp; /**/unsigned long lastConnectionTime = 0; // время последней передачи данных /**/String Hostname; //имя железки - выглядит как ESPAABBCCDDEEFF т.е. ESP+mac адрес. float dhttemp; // буферная переменная для хранения температуры от dht22 int dhthum; // буферная переменная для хранения влажности от dht22 int topwm; // хранит значение для шима яркости дисплея void wifimanstart() { // Волшебная процедура начального подключения к Wifi. // Если не знает к чему подцепить - создает точку доступа ESP8266 и настроечную таблицу http://192.168.4.1 // Подробнее: https://github.com/tzapu/WiFiManager WiFiManager wifiManager; wifiManager.setDebugOutput(debug); wifiManager.setMinimumSignalQuality(); if (!wifiManager.autoConnect("ESP8266")) { if (debug) Serial.println("failed to connect and hit timeout"); delay(3000); //reset and try again, or maybe put it to deep sleep ESP.reset(); delay(5000); } if (debug) Serial.println("connected..."); } void setup() { // pinMode(14, OUTPUT); // gpio14 будет шимить подсветку дисплея Hostname = "ESP"+WiFi.macAddress(); Hostname.replace(":",""); Serial.begin(115200); // инициализация экрана lcd.begin(4, 5); // sda=gpio04, scl=gpio05 lcd.backlight(); // инициализация датчика температуры и давления bmp180 dht.begin(); if (!bmp.begin()) // если датчик не обнаружен, сообщаем об этом в компорт и на дисплей { Serial.println("Could not find BMP180 or BMP085 sensor at 0x77"); lcd.clear(); lcd.print("BMP180 FAILED"); while (1) {} } WiFi.hostname(Hostname); wifimanstart(); Serial.println(WiFi.localIP()); Serial.println(WiFi.macAddress()); Serial.print("Narodmon ID: "); Serial.println(Hostname); // выдаем в компорт мак и айпишник железки, так же выводим на дисплей lcd.clear(); lcd.setCursor(0, 0); lcd.print(WiFi.localIP()); lcd.setCursor(0, 1); lcd.print(Hostname); lastConnectionTime = millis() - postingInterval + 15000; //первая передача на народный мониторинг через 15 сек. } void WriteLcdTemp (void){ // заполнение дисплея. происходит каждые 5 минут после получения данных с датчиков lcd.clear(); lcd.setCursor(1, 0); lcd.print("T1 "); lcd.setCursor(4, 0); lcd.print(bmp.readTemperature()); lcd.setCursor(8,0); lcd.print(" P "); lcd.setCursor(11,0); lcd.print(bmp.readPressure()/133.3); lcd.setCursor(1, 1); lcd.print("T2 "); lcd.setCursor(4, 1); lcd.print(dhttemp); lcd.setCursor(8,1); lcd.print(" H "); lcd.setCursor(11,1); lcd.print(dhthum); lcd.setCursor(13,1); lcd.print("%"); } bool SendToNarodmon() { // Собственно формирование пакета и отправка. WiFiClient client; String buf; buf = "#" + Hostname + "#ESP_YOBA" + "\r\n"; // заголовок И ИМЯ, которое будет отображаться в народном мониторинге, чтоб не палить мак адрес dhttemp=dht.readTemperature(); // сохравняем в буферные переменные данные с dht22, чтобы удобно было оперировать dhthum=dht.readHumidity(); buf = buf + "#T1#" + String(bmp.readTemperature()) + "\r\n"; //температура bmp180 buf = buf + "#T2#" + String(dhttemp) + "\r\n"; //температура dht22 buf = buf + "#H1#" + String(dhthum) + "\r\n"; //влажность buf = buf + "#P1#" + String(bmp.readPressure()) + "\r\n"; //давление buf = buf + "##\r\n"; // закрываем пакет if (!client.connect("narodmon.ru", 8283)) { // попытка подключения Serial.println("connection failed"); lcd.clear(); lcd.print("connect failed"); return false; // не удалось; } else { WriteLcdTemp(); client.print(buf); // и отправляем данные if (debug) Serial.print(buf); while (client.available()) { String line = client.readStringUntil("\r"); // если что-то в ответ будет - все в Serial Serial.print(line); } } return true; //ушло } void loop() { //автояркость topwm=map(analogRead(A0), 0, 1023, 10, 950); analogWrite(14, topwm); delay(100);// нужна, беж делея у меня не подключался к вайфаю if (millis() - lastConnectionTime > postingInterval) {// ждем 5 минут и отправляем if (WiFi.status() == WL_CONNECTED) { // ну конечно если подключены if (SendToNarodmon()) { lastConnectionTime = millis(); }else{ lastConnectionTime = millis() - postingInterval + 15000; }//следующая попытка через 15 сек }else{ lastConnectionTime = millis() - postingInterval + 15000; Serial.println("Not connected to WiFi"); lcd.clear(); lcd.print("No WiFi");}//следующая попытка через 15 сек } yield(); // что за команда - фиг знает, но ESP работает с ней стабильно и не глючит. }

Что умеет устройство?
Устройство производит измерение температуры, давления (датчик bmp180) и влажности (датчик dht22), отображает показания на дисплее, и отправляет на сайт народного мониторинга.
При первом после прошивки включении, или при отсутствии знакомых wifi сетей устройство прикидывается точкой доступа открытого типа с именем ESP8266.

В com порт устройство шлет это:

Необходимо подключиться с телефона, ноутбука или планшета к точке с именем ESP8266, и пройти по адресу 192.168.4.1
Откроется такая страничка:


Нажимаем кнопку Configure Wifi, и попадаем на такую страничку


Далее выбираем свою сеть, вводим пароль, жмем Save. Готово, устройство само перезагружается, и начинает работать. Сначала на дисплей и в com порт выводится ip адрес и mac адрес устройства с префиксом ESP, его надо использовать в качестве id датчика при регистрации на сайте народного мониторинга, а через 15 секунд на дисплей, в com порт, и на сайт народного мониторинга выводятся первые показания с датчиков.
T1 - температура с bmp180
T2 - температура с dht22
P - атмосферное давление. На дисплей выводится в мм. рт. ст., на народный мониторинг идет в не помню каких единицах, преобразуется в мм. рт. ст. автоматически.
Н - влажность в процентах.


В строчках Т2 и Н ересь, потому что не подключен датчик dht22

В таком виде конструкция проработала пару недель, пока я потихоньку продумывал готовое устройство.
Готовое устройство решил собирать на модуле (такой же стоит на nodemcu)
Быстренько развел и вытравил плату.




Код менять не пришлось вообще. Номера портов для датчиков те же, необходимо только подключить голую esp8266 по типовой схеме, чтобы она могла работать, и можно было заливать прошивку. 3.3 вольта получил с помощью стабилизатора ams 1117 3.3v.
Питается устройство от телефонной зарядки 5в. 2А. Но и одного ампера, думаю, будет достаточно.


Для прошивки вывел отдельный 3-pin разъем с контактами RX, TX и GND.

Прошивал готовое устройство с помощью адаптера usb - uart. Плюс его в том, что на нем имеется переключатель 5v-3.3v, хотя знающие ребята говорят, что этот переключатель работает только для питания, а уровень rx и tx не меняется. Но у меня все работало без преобразования уровней, наверное потому, что esp8266 толерантна к 5v на rx tx, хоть и работает от 3.3.
Так же на готовой плате был предусмотрен переключатель для ввода в режим прошивки (красный ползунковый, нижний ползунок для ввода в режим прошивки. Верхний ползунок я подключил на gpio12, на всякий случай, возможно для переключения режимов индикации на будущее, а пока он не задействован. Можно не мудрить, и просто поставить перемычку между gpio0 и GND)

Для прошивки нужно подключить usb-uart преобразователь таким образом:
esp rx - uart tx
esp tx - uart rx
esp gnd - uart gnd

Затем подключить gpio0 к земле с помощью нижнего ползунка на переключателе, нажать кнопку «Reset» на плате (находится сверху), в arduinoIDE выбрать нужный com port, скорость загрузки (115200 для большинства плат esp8266), и нажать кнопку «Загрузка».
После окончания загрузки устройство начнет работать, переключатель прошивки нужно возвратить в положение OFF, иначе при следующей перезагрузке или отключении питания устройство опять войдет в режим прошивки.

Вспомним сдохший датчик dht22. Симптомы - постоянно показывает влажность 99.9, в сухую погоду может показывать меньше. Сдох он осенью 2016 года, исправно проработав всю весну и лето. Была неделя постоянных дождей, и мне на электропочту свалилось письмо от народного мониторинга, что, мол, ваш датчик несколько дней подряд показывает одно и то же. Я не стал менять датчик, а просто спрятал его из общего доступа до недавнего момента.
Возможной причиной помирания считаю тесный корпус. В качестве его основы было использовано яйцо от киндерсюрприза. Был разработан новый, более технологичный и просторный корпус из подрозетника и заглушки канализационной трубы. Вот оба корпуса рядом:


Плату датчиков не переделывал, просто заменил дохлый dht22 на живой, покрыл цапонлаком и закрепил термосоплями в верхней части уличного корпуса.
Вот так выглядит схема платы датчиков. Нарисовать можно даже маркером

схема и внешний вид




Закрепил датчики на окне, шлейф завел в комнату. Вынос можно было сделать и побольше, да и от внешнего блока кондиционера убрать, но это проблематично.

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

В собранном виде

Испытание влажностью пройдено успешно. Датчик жив.

Это был полезный опыт для меня, я наконец то пощупал esp8266, и получил стабильно работающее полезное мне устройство.
Надеюсь кому то пригодится эта статья. Если возникнут трудности или вопросы, пишите в личку, отвечу.

Планирую купить +121 Добавить в избранное Обзор понравился +108 +214

Продолжаем развивать нашу метеостанцию.

Перед тем, как перейти к обновлению, хочу внести немного ясности.

Мне написал один из наших коллег с вопросом, по какой причине введен сторожевой таймер?

Сторожевой таймер стоит на случай ч.п. Как показывает практика, ENC28J60 не тянет более (если не подводит память) 4 одновременных соединений. Учитывая сколько служебных соединений, постоянно происходит для поддержания работы самой сети, и просто левый трафик, создаваемый всяческими домашними игрушками (например, современные телевизоры, сканируют доступные хосты в сети и открытые у них порты) конструкция попросту уходит в ступор. ENC28J60 не умеет самостоятельно работать с сетевыми протоколами и все реализовано в библиотеках. Возможно дело именно в них.
Проверял все доступные библиотеки и разные модули (вдруг брак), но добиться стабильной работы в течении длительного времени у меня не получилось. Максимальный срок был порядка 3-4 недель.
Именно для этого там крутится "пес" и в случае чего дергает контроллер. После этого проблема ушла.
Также не отрицаю, что возможно в моей домашней сети есть определенные нюансы или проблемы. Но раз проблема была у меня, она может выплыть и у другого человека. Я пока нашел только такое решение.
Насколько мне известно, на чипах от Wiznet (W5100 и выше) этого нет, ну или просто плохо искали.

Переходим к обновлению

Самое главное, мы уходим от чипа ENC28J60 и переходим на W5100 . Я пытался реализовать все на старом чипе, но не хватает памяти микроконтроллера из-за очень больших библиотек для ENC28J60 . При использовании нового чипа, стандартной библиотеки от разработчика и всех внесенных изменений, остается еще более 20% свободной памяти микроконтроллера ATMega328 . А это, новые плюшки!

В этой версии (назовем её второй) добавлена возможность передачи показаний с датчиков по беспроводной связи используя частоту 433 мГц . Сами модули я брал у Китайцев, маркировка XY-MK-5V . Хочу отметить, что качество передачи далеко от совершенства. Возможны потери сигнала, шумы, не возможность одновременной передачи и т.д и т.п. Но их цена (менее $1 за комплект) компенсируют эти недостатки. Скажу Вам по секрету, что именно эти (самые дешевые) модули стоят во многих фирменных метеостанциях для домашнего использования. Ого, неожиданно?

Начнем с базовой станции

Мы переходим на Arduino UNO и Ethernet Shield (первой версии) на базе чипа W5100 . Это бутерброд и описывать его нету смысла. Я опишу только дополнительно задействованные контакты для модулей XY-MK-5V .

Модуль передатчика использует питание 5V , GND (куда без матушки то) и D2 пин на контроллере. Изменить контакт D2 (DATA) можно, используя функцию vw_set_tx_pin из библиотеки vw.

В отличии от предыдущего скетча, в этом задействованы две дополнительные библиотеки:

#include #include

Сам скетч

Скрытый текст

#include #include #include #include #include #include #include #include #define DHTTYPE DHT22 #define DHTPIN 5 DHT dht(DHTPIN, DHTTYPE); byte mac = {0x54, 0x34, 0x31, 0x31, 0x31, 0x31}; char server = "narodmon.ru"; int port = 8283; IPAddress ip(192,168,0,201); EthernetClient client; BMP085 dps = BMP085(); long Temperature = 0, Pressure = 0; float H, dP, dPt; bool interval = true; EasyTransferVirtualWire ET; struct SEND_DATA_STRUCTURE{ byte ID; // Идентификатор устройства int Temperature; // Температура float Pressure; // Давление float Humidity; // Влажность float dewPoint; // Точка росы/инея }; SEND_DATA_STRUCTURE broadcast; void setup() { // Инициализация сторожевого таймера (Watchdog timer) wdt_disable(); delay(8000); wdt_enable(WDTO_8S); // Инициализация консоли Serial.begin(9600); // Инициализация датчика DHT dht.begin(); // Инициализация модуля 433 мГц ET.begin(details(broadcast)); vw_set_ptt_inverted(true); vw_set_tx_pin(2); vw_setup(2000); // Стартуем сеть, если не дождались данных с DHCP сервера то // присваеваем себе адрес самостоятельно if (Ethernet.begin(mac) == 0) Ethernet.begin(mac, ip); // Инициализация 1-Wire Wire.begin(); delay(200); // Инициализация BMP180 с корректировкой высоты // dps.init(MODE_STANDARD, 3200, true); // Инициализация BMP180 dps.init(); Serial.println(Ethernet.localIP()); // Отправляем первые данные сразу после включения устройства send_info(true); } // dewPoint function NOAA // reference (1) : http://wahiduddin.net/calc/density_algorithms.htm // reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm double dewPoint(double celsius, double humidity) { // (1) Saturation Vapor Pressure = ESGG(T) double RATIO = 373.15 / (273.15 + celsius); double RHS = -7.90298 * (RATIO - 1); RHS += 5.02808 * log10(RATIO); RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO))) - 1) ; RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ; RHS += log10(1013.246); // factor -3 is to adjust units - Vapor Pressure SVP * humidity double VP = pow(10, RHS - 3) * humidity; // (2) DEWPOINT = F(Vapor Pressure) double T = log(VP/0.61078); // temp var return (241.88 * T) / (17.558 - T); } void send_info(bool eth) { bool fail = true; while(fail) { // Пытаемся считать данные с датчика влажности DHT до тех пор, пока не получим // результат. В 90% случаев все работает нормально, но нам нужны 100% if((H = dht.readHumidity()) >= 0) { // Получение влажности и температуры с датчика BMP180 dps.getPressure(&Pressure); dps.getTemperature(&Temperature); // Подсчитываем точку росы, если температура на улице выше 0 градусов Цельсия // и ожидаем результат выше 0, в противном случае выводим 0. Это необходимо // чтобы не вводить в заблуждения в зимее время года. // dP = Temperature>0?((dPt=dewPoint(Temperature*0.1, H))<0?0:dPt):0; dP = dewPoint(Temperature*0.1, H); // Отправляем данные в эфир 433 мГц broadcast.ID = 1; broadcast.Temperature = floor(Temperature*0.1); broadcast.Pressure = floor(Pressure/133.3*10)/10; broadcast.Humidity = floor(H*10)/10; broadcast.dewPoint = floor(dP*10)/10; ET.sendData(); delay(250); if(eth) { // Подключаемся к серверу "Народный мониторинг" if(client.connect(server, port)) { // Начинаем передачу данных // адрес_устройства_в_проекте, имя_устройства, GPS широта, GPS долгота client.print(F("#fe-31-31-0e-5a-3b#Arduino Uno#71.344699#27.200014\n")); // Температура client.print(F("#T0#")); client.print(Temperature*0.1); client.print(F("#Температура\n")); // Давление client.print("#P1#"); client.print(Pressure/133.3); client.print(F("#Давление\n")); // Влажность client.print("#H1#"); client.print(H); client.print(F("#Влажность\n")); // Точка росы\инея client.print("#T1#"); client.print(dP); client.print((dP <= 0)? F("#Точка инея\n"):F("#Точка росы\n")); //client.print(F("#Точка росы\n")); // Отправляем конец телеграммы client.print("##"); // Даем время отработать Ethernet модулю и разрываем соединение delay(250); client.stop(); } } // Останавливаем цикл, если передача завершена fail = !fail; break; } delay(250); } } void loop() { // Каждые 4 секунды сбрасываем сторожевой таймер микроконтроллера // Каждые 6 минут отправляем данные на "Народный мониторинг" // Каждые 30 секунд отсылаем данные в эфир 433 if(!(millis()%1000)) wdt_reset(); if(!(millis()%360000)) send_info(true); if(!(millis()%30000)) send_info(false); }

К самим модулям необходимо добавить антенну. Для 433 мГц достаточно обычного медного провода длинной 17 см . Без антенны можете забыть о нормальной работе.

Переходим к самой важной части этого обновления - локальная беспроводная станция

Для её реализации (на коленке) я использовал аналог Arduino NANO (на базе ATMega328 ) и TFT дисплей на чипе ST7735S с разрешением 128 x 160

Скрытый текст



Распиновка дисплей -> контроллер

============================= LED | 3.3V SCK | SCK (13) SDA | MOSI (11) A0 | DC (9) RESET | RST (8) CS | CS (10) GND | GND VCC | 5V ============================

Модуль приемник подключается также как передатчик, только DATA к пину D7 .

Пару снимков, как это выглядит:

Скрытый текст

Скетч приемника

Скрытый текст

#include #include #include #include int x, y; int w = 128, h = 160; int size; // 433 EasyTransferVirtualWire ET; struct SEND_DATA_STRUCTURE{ byte ID; // Идентификатор устройства int Temperature; // Температура float Pressure; // Давление float Humidity; // Влажность float dewPoint; // Точка росы/инея }; SEND_DATA_STRUCTURE broadcast; int Log_Temperature = -1; float Log_Pressure = -1; float Log_Humidity = -1; float Log_dewPoint = -1; // TFT #define cs 10 #define dc 9 #define rst 8 char Temperature, Pressure, Humidity, dewPoint; String info; TFT TFTscreen = TFT(cs, dc, rst); void setup(){ Serial.begin(9600); // Инициализация модуля 433 мГц ET.begin(details(broadcast)); vw_set_ptt_inverted(true); vw_set_rx_pin(7); vw_setup(2000); vw_rx_start(); // Инициализация и начальная настройка дисплея TFTscreen.begin(); TFTscreen.setRotation(2); TFTscreen.background(0, 0, 0); // Рисуем статические элементы // 1. Заходите к нам в гости TFTscreen.stroke(255, 255, 255); TFTscreen.setTextSize(1); TFTscreen.text(" ", 10, 10); // 2. Описание показаний с датчиков TFTscreen.text("mmHg", w/2+5, 80); TFTscreen.text("%", w/2+5, 100); TFTscreen.text("C", w/2+5, 120); broadcast.Temperature = 0; broadcast.Pressure = 0; broadcast.Humidity = 0; broadcast.dewPoint = 0; TFTPrint(); } void loop(){ if(ET.receiveData()){ if(broadcast.ID == 1) TFTPrint(); /* Serial.println(broadcast.Temperature); Serial.println(broadcast.Pressure); Serial.println(broadcast.Humidity); Serial.println(broadcast.dewPoint); Serial.println(); */ } } void changes(int size, int x, int y, bool up, bool clear = false) { if(clear) TFTscreen.stroke(0, 0, 0); else { changes(size, x, y, !up, true); TFTscreen.stroke((up)?0:255, 0, (up)?255:0); } if((size%2) == 0) size++; while(size > 0) { TFTscreen.line(x, y, x+(size--), y); ++x, (up)?--y:++y, --size; } /* while(size > 0) { TFTscreen.line(x, y, (up)?x+size-1:x, (up)?y:y+size-1); ++x, ++y, --size; } */ } int x_center(int w, int length, int size) { return floor((w-length*(size*5)+size*2)/2); } int x_alignment_right(int w, int length, int size) { return ceil(w-length*(size*5)+size*2); } void TFTPrint() { size = 3; // ================================================================================== // Вывод показаний температуры // ================================================================================== if(broadcast.Temperature != Log_Temperature) { TFTscreen.setTextSize(size); // Затираем устаревшие данные String info = String(Log_Temperature); info.concat(" C"); if(Log_Temperature > 0) info = "+"+info; info.toCharArray(Temperature, info.length()+1); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35); // Выводим новые показания info = String(broadcast.Temperature); info.concat(" C"); if(broadcast.Temperature > 0) info = "+"+info; info.toCharArray(Temperature, info.length()+1); // Меняем цвет значения температуры в зависимости от самой температуры int r, g = 0, b; if(broadcast.Temperature > 0) { r = map(broadcast.Temperature, 0, 40, 255, 150); // Красный b = map(broadcast.Temperature, 0, 40, 30, 0); // Изменяем оттенок для более наглядного перехода через ноль } else { r = map(broadcast.Temperature, -40, 0, 0, 30); // Изменяем оттенок для более наглядного перехода через ноль b = map(broadcast.Temperature, -40, 0, 150, 255); // Синий } TFTscreen.stroke(b, g, r); // ВНИМАНИЕ: в библиотеке перепутаны позиции цветов, место RGB используется BGR! TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35); } size = 1; // ================================================================================== // Вывод показаний давления // ================================================================================== if(broadcast.Pressure != Log_Pressure) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_Pressure); info.toCharArray(Pressure, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80); // Выводим новые показания info = String(broadcast.Pressure); info.toCharArray(Pressure, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80); changes(10, 106, 85, (broadcast.Pressure > Log_Pressure)?true:false); } else { changes(10, 106, 85, true, true); changes(10, 106, 85, false, true); } // ================================================================================== // Вывод показаний влажности // ================================================================================== if(broadcast.Humidity != Log_Humidity) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_Humidity); info.toCharArray(Humidity, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100); // Выводим новые показания info = String(broadcast.Humidity); info.toCharArray(Humidity, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100); changes(10, 106, 105, (broadcast.Humidity > Log_Humidity)?true:false); } else { changes(10, 106, 105, true, true); changes(10, 106, 105, false, true); } // ================================================================================== // Вывод показаний точки росы\инея // ================================================================================== if(broadcast.dewPoint != Log_dewPoint) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_dewPoint); info.toCharArray(dewPoint, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120); // Выводим новые показания info = String(broadcast.dewPoint); info.toCharArray(dewPoint, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120); changes(10, 106, 125, (broadcast.dewPoint > Log_dewPoint)?true:false); } else { changes(10, 106, 125, true, true); changes(10, 106, 125, false, true); } // Обновляем значения в логах для последующего сравнения показаний Log_Temperature = broadcast.Temperature; Log_Pressure = broadcast.Pressure; Log_Humidity = broadcast.Humidity; Log_dewPoint = broadcast.dewPoint; }

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

Как мне показалось, дизайн это та часть проекта, которая отнимает большую часть времени!

Скрытый текст

Часть данных сфабрикованы для отображения некоторых элементов дизайна.

Артефакты на дисплее, это пыль и прочая грязь скопившаяся за долго время нахождения дисплея в... где то там, ... ну там, не помню откуда его достал! Отстаньте!

В скетче имеются функции позиционирования. Они довольно примитивны, но позволяют добиться определенных эффектов.

  1. x_center
  2. x_alignment_right

Первая производит центровку текста, а вторая выравнивание по правой части указанной зоны. Все вычисления производятся относительно размеров заданного текста, исходя из выражения 1 size = 1PX х 1PX сегмента шрифта.

На дисплее также отображаются элементы соответствующие повышению или понижению той или оной величины показаний. Отображаются они в виде треугольников. Но в коде функции changes есть альтернативное отображение в виде треугольников повернутых на 45 градусов. Если показания повышаются то элемент красный, в противном случае, синий.

Кстати, цвет и оттенок основной температуры изменяется в зависимости от самой температуры. Довольно спорное решение, но на мой взгляд, визуально комфортное. Я некоторое время бился над ней, и понял, что значения в функции stroke , объекта TFT дисплея, указаны в неверном порядке. BGR место RGB . Это ошибка разработчика, ну или я что-то не понимаю.

PS : Все довольно интересно, но на мой взгляд заслуживает дальнейшего развития. Чем и займемся через какое то время.

 

 

Это интересно: