
Наткнулся тут на обзоры всяких датчиков от Sanja, этот добрый человек прислал ссылки на запчасти, что мне надо закупить (плата с модулем WiFi NodeMCU ESP8266 и датчики DS18b20) и даже ролик на ютубе, показывающий, что надо делать и как. В принципе, этих ссылок достаточно для создания аналогичного устройства, но были и сложности, о которых я и хочу рассказать, дабы еще более упросить повторение процесса для таких же новичков в этом деле, как я)
Это мой первый опыт использования подобной техники и программирования контроллеров, поэтому используемые методы пайки или монтирования могут повергнуть в шок бывалых мастеров… Впечатлительных прошу отдалиться от мониторов). Также я не стал заморачиваться с печатью корпуса для устройства, а использовал пластиковую бутылку (почти), как научила меня передача «Очумелые ручки» в свое время :)
Итак, нам понадобится:
- Плата с модулем WiFi NodeMCU ESP8266 ~220 руб
- Датчики Dallas DS18b20 по ~80 руб за штуку
- Резистор 4к7 маломощный ~5 руб
- Разъемы «мама» для подключения к ножкам Arduino и подобным ~120руб за 40 штук
- WiFi роутер (если в предполагаемом для установки помещении его еще нет) ~400 руб б/у
- Блок питания USB (1А, говорят, достаточно, но я брал на 2А) и кабель microUSB для программирования (при подключении к компу) и питания ~200 руб
Сначала параллельно соединяем все датчики, которые нам нужны, в одно целое


Потом, конечно, это все тщательно замотано изолентой/термоусадкой.
Подключаем к пинам по схеме, плату подсоединяем USB-кабелем к компьютеру, запускаем свежеустановленный Arduino 1.8.2 и настраиваем его, как описано тут. Порт меняем на COM4 (если плата уже подключена).
Распаковываем архив в папку, где у вашей Arduino лежат скетчи. В комплекте уже есть библиотеки, которые было так тяжело найти подходящие, пол дня убил на это.
Загружаем сам скетч sketch_esp8266dallas в среду разработки.
Меняем там на строках 14 и 15 название WiFi сети и пароль к ней, а также на 45 строке адрес сервера, на котором будут сохраняться передаваемые параметры. Серверная часть (PHP) описана ниже.
К слову, мы могли бы уже здесь отправлять уведомления в Telegram, если температуры ниже заданного уровня, немного изменив код отправки GET-запроса, но Telegram у нас в России под запретом, поэтому существуют некоторые проблемы с использованием его API. Альтернативный вариант — использовать API от sms.ru, но это уже совсем другая история. Главное, что сюда можно вписать абсолютно любое обращение к какому угодно сервису, будь то «народный мониторинг» или любой другой.
Пробуем скомпилировать скетч через меню «Скетч — Проверить/Компилировать», если все хорошо и ошибок нет, то можно загружать в плату «Скетч — Загрузка».
Открываем меню «Инструменты — Монитор порта» и видим примерно такую картину:
А если у вас картина другая и плата WiFi-соединение поднимает, но запросы нифига не идут, то скорее всего роутер находится слишком далеко и сигнал слабый. У меня роутер стоял в соседней комнате (!) и я пару часов убил на выяснение, почему же не работает сервис Blink или любой другой. Когда ты этим занимаешься первый раз, сложно понять, что плате просто не хватает мощности сигнала, ведь к сети то она подцепиться смогла, причем с первого раза. И именно по этой причине пришлось купить дополнительный роутер, потому что в подвале, где я хотел расположить датчик, ситуация с WiFi была бы еще хуже. Я даже посмотрел кучку обзоров на разные вариации этой платы и анализ мощности приема WiFi-сигнала, а также возможность использования дополнительных антенн, но найденные примеры не демонстрировали существенного эффекта.
Тут мне пришла в голову мысль, что вместо WiFi-платы можно было бы использовать сразу модуль с RJ-45 на борту, но было уже поздно :)
Сигналы датчиков обрабатываются успешно, запросы отправляются, теперь приступим к серверной части.
У меня, как у любого порядочного веб-программиста, имеется с пяток хостинговых площадок в распоряжении, поэтому вопроса о том, где разместить серверную часть, не возникло — уже работал в ТСЖ сайт на вордпрессе. Но есть и варианты бесплатного хостинга с PHP и MySQL, я ими не пользовался и ссылок ставить не буду, но найти их легко в Яндексе.
Далее нам нужно через PHPMyAdmin создать табличку в любой имеющейся или новой базе с названием temperatures и полями: datatime (тип DATETIME), t1 (DECIMAL), t2 (DECIMAL), t3 (DECIMAL), t4 (DECIMAL).
Распаковываем архив серверной части через ftp у себя на сервере и правим в файлах index.php и json.php параметры соединения с базой данных и в index.php внешний адрес скрипта для получения данных.
В коде нет ничего невероятного, если кому-то лень скачивать архив, то вот содержимое основных php-скриптов:
<?php // Параметры соединения с базой данных $db_server = 'localhost'; $db_name = '*****'; $db_user = '*****'; $db_passwd = '******'; // Подключение к базе данных $db_connection = @mysql_connect($db_server, $db_user, $db_passwd); if (!$db_connection || !@mysql_select_db($db_name, $db_connection)) { echo "Error during SQL connection"; } mysql_select_db($db_name, $db_connection); // Сохранение в базу переданных с контроллера данных if(isset($_GET['t1']) && isset($_GET['t2']) && isset($_GET['t3']) && isset($_GET['t4'])) { $t1 = floatval($_GET['t1']); $t2 = floatval($_GET['t2']); $t3 = floatval($_GET['t3']); $t4 = floatval($_GET['t4']); if($t1>0 && $t2>0 && $t3>0&& $t4>0) { $result = mysql_query(" INSERT INTO `temperatures` (datatime,t1,t2,t3,t4) VALUES (now(), $t1, $t2, $t3, $t4)"); echo mysql_error(); // Если температуры ниже минимума, отправим сообщение в Telegram if($t4<40 || $t2<35 || $t1<50 || $t3<50) { include 'tele.php'; message_to_telegram("ГВС_П: $t4, ГВС_О: $t2, Котельная_П: $t1, Котельная_О: $t3"); } } die(); } ?> <html> <head> <title>Температурные данные дома №*** по ул.*********</title> <link href="/temp/style.css" rel="stylesheet"> <link href="/temp/jquery.datetimepicker.min.css" rel="stylesheet"> <script type="text/javascript" src="/temp/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/temp/moment.js"></script> <script type="text/javascript" src="/temp/jquery.datetimepicker.full.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class='current row'> <div class='col time'></div> <div class='t1 col'>ГВС, подача: <b></b></div> <div class='t2 col'>ГВС, обратка: <b></b></div> <div class='t3 col'>Котельная, подача: <b></b></div> <div class='t4 col'>Котельная, обратка: <b></b></div> </div> <div class="row selectperiod"> <div class="col"><input type="text" id="datastart" value="" class="form-control"/></div> <div class="col"><input type="text" id="dataend" value="" class="form-control"/></div> <div class="col">Точность: <select id="intervalMinutes"> <option value="1">1 мин</option> <option value="5">5 мин</option> <option value="15">15 мин</option> <option value="30" selected>30 мин</option> <option value="6">1 час</option> </select> </div> </div> <div id="chartContainer" style="position: relative; height:40vh; width:90vw;margin: 0 auto;"> <canvas id="myChart"></canvas> </div> <script> // Создание и отрисовка графика function getChart(dataFromJSON) { var dates = [], t1arr = [], t2arr = [], t3arr = [], t4arr = []; dataFromJSON.forEach(function(item, i, arr) { dates.push(item.datatime); t1arr.push(item.t1); t2arr.push(item.t2); t3arr.push(item.t3); t4arr.push(item.t4); }); var ctx = document.getElementById("myChart").getContext('2d'); window.myChart = new Chart(ctx, { type: 'line', data: { labels: dates, datasets: [{ label: 'ГВС, подача', data: t1arr, borderColor: '#ffc700', backgroundColor: 'rgba(255,255,255,0)' },{ label: 'ГВС, обратка', data: t2arr, borderColor: '#3bd100', backgroundColor: 'rgba(255,255,255,0)' },{ label: 'Контур котельной, подача', data: t3arr, borderColor: '#ff0000', backgroundColor: 'rgba(255,255,255,0)' },{ label: 'Контур котельной, обратка', data: t4arr, borderColor: '#ee00ff', backgroundColor: 'rgba(255,255,255,0)' }] }, options: { responsive: true, //maintainAspectRatio: false, legend: { display: false }, scales: { xAxes: [{ type: 'time', time: { unit: 'minute' } }] } } }); } // Обновление графика данными function updateChart(dataFromJSON) { var dates = [], t1arr = [], t2arr = [], t3arr = [], t4arr = []; dataFromJSON.forEach(function(item, i, arr) { dates.push(item.datatime); t1arr.push(item.t1); t2arr.push(item.t2); t3arr.push(item.t3); t4arr.push(item.t4); }); window.myChart.data.labels = dates; window.myChart.data.datasets[0].data = t1arr; window.myChart.data.datasets[1].data = t2arr; window.myChart.data.datasets[2].data = t3arr; window.myChart.data.datasets[3].data = t4arr; window.myChart.update(); } // Получение данных для графика function doUpdateChart() { $.getJSON( "http://адрес вашего сайта и путь к файлу/temp/json.php?datastart="+$("#datastart").val()+"&dataend="+$("#dataend").val()+"&intervalMinutes="+$("#intervalMinutes").val(), function( data ) { updateChart(data); }); } // Получение текущих данных function doUpdateLastValues() { $.getJSON( "http://адрес вашего сайта и путь к файлу/json.php?last=true", function( data ) { $(".time").html((data[0].datatime+'').replace(' ', ' ')); $(".t1 b").text(data[0].t1+"°C"); $(".t2 b").text(data[0].t2+"°C"); $(".t3 b").text(data[0].t3+"°C"); $(".t4 b").text(data[0].t4+"°C"); }); } $(function() { // Выбор дат $.datetimepicker.setLocale('ru'); $('#datastart,#dataend').datetimepicker({ format:'d.m.Y H:i', lang:'ru', minDate:'20.11.2018', maxDate:Date.now(), formatDate:'d.m.Y', dayOfWeekStart: 1, i18n:{ ru:{ months:[ 'Январь','Февраль','Март','Апрель', 'Май','Июнь','Июль','Август', 'Сентябрь','Октябрь','Ноябрь','Декабрь', ], dayOfWeek:[ "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", ] } }, }); // Загрузка текущих данных doUpdateLastValues(); // Заполнение графика $.getJSON( "http://адрес вашего сайта и путь к файлу/temp/json.php?datastart="+$("#datastart").val()+"&dataend="+$("#dataend").val()+"&intervalMinutes="+$("#intervalMinutes").val(), function( data ) { getChart(data); }); window.lastDataStart = $("#datastart").val(); window.lastDataEnd = $("#dataend").val(); window.intervalMinutes = $("#intervalMinutes").val(); var winHeight = $(window).height(); setInterval(function() { var changed = false; if($("#datastart").val() != window.lastDataStart) { changed = true; window.lastDataStart = $("#datastart").val(); } if($("#dataend").val() != window.lastDataEnd) { changed = true; window.lastDataEnd = $("#dataend").val(); } if($("#intervalMinutes").val() != window.intervalMinutes) { changed = true; window.intervalMinutes = $("#intervalMinutes").val(); } if(changed) doUpdateChart(); }, 1000); // Обновлять текущие данные раз в 30 сек setInterval(function() { doUpdateLastValues(); doUpdateChart(); }, 30000); }); </script> </body> </html>
<?php ini_set('error_reporting', E_ALL); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); // Параметры соединения с базой данных $db_server = 'localhost'; $db_name = '*****'; $db_user = '*****'; $db_passwd = '*****'; // Вычисление разницы дат function dateDifference($date_1 , $date_2 , $differenceFormat = '%a' ) { $datetime1 = date_create($date_1); $datetime2 = date_create($date_2); $interval = date_diff($datetime1, $datetime2); return $interval->format($differenceFormat); } // Подключение к базе данных $db_connection = @mysql_connect($db_server, $db_user, $db_passwd); if (!$db_connection || !@mysql_select_db($db_name, $db_connection)) { echo "Error during SQL connection"; } mysql_query("set names utf8"); // Если запрошены текущие данные if(isset($_GET['last'])) { $result = mysql_query(" SELECT DATE_FORMAT(DATE_ADD(datatime, INTERVAL 1 HOUR),'%d.%m.%Y %H:%i:%s') datatime,t1 t3,t2 t2,t3 t4,t4 t1 FROM temperatures ORDER BY datatime DESC LIMIT 1"); $data = []; while($row = mysql_fetch_assoc($result)) { $data[] = $row; } // Выводим только их и заканчиваем на этом die(json_encode($data )); } $intervalMinutes = 30; $datastart = date_add(date_create(date('Y-m-d H:i:s')), date_interval_create_from_date_string('-1 days')); $dataend = date_add(date_create(date('Y-m-d H:i:s')), date_interval_create_from_date_string('1 hour')); if(isset($_GET['intervalMinutes'])) $intervalMinutes = intval($_GET['intervalMinutes']); // Если слишком большой временной промежуток, то не будем наружать сервер большим объемом данных, округлим побольше $difHours = date_diff($datastart, $dataend); if($difHours->format('%days')>3) $intervalMinutes = 60; if($difHours->format('%days')>10) $intervalMinutes = 180; if(isset($_GET['datastart']) && $_GET['datastart']!="") $datastart = date_create($_GET['datastart']); if(isset($_GET['dataend']) && $_GET['dataend']!="") $dataend = date_create($_GET['dataend']); $result = mysql_query(" SELECT roundeddatetime datatime, AVG(t1) t3,AVG(t2) t2,AVG(t3) t4,AVG(t4) t1 FROM ( SELECT DATE_FORMAT(DATE_ADD(DATE_ADD(datatime, INTERVAL (".$intervalMinutes."-MINUTE(datatime)%".$intervalMinutes.") MINUTE), INTERVAL 1 HOUR),'%Y-%m-%d %H:%i:00') roundeddatetime,t1,t2,t3,t4 FROM temperatures WHERE datatime>DATE_ADD('".$datastart->format('Y-m-d H:i:s')."', INTERVAL -1 HOUR) AND datatime<DATE_ADD('".$dataend->format('Y-m-d H:i:s')."', INTERVAL -1 HOUR) ) t1 GROUP BY roundeddatetime LIMIT 3440"); $data = []; while($row = mysql_fetch_assoc($result)) { $data[] = $row; } echo json_encode($data ); ?>
<? // сюда нужно вписать токен вашего бота define('TELEGRAM_TOKEN', '*****:********'); // сюда нужно вписать ваш внутренний айдишник чата define('TELEGRAM_CHATID', '414951********599'); //echo message_to_telegram('Данные!'); function message_to_telegram($text) { $ch = curl_init(); $url = "https://api.telegram.org/bot".TELEGRAM_TOKEN."/sendMessage?chat_id=".TELEGRAM_CHATID."&text=".$text; // где XXXXX - ваши значения $prxy = '162.33.68.41:56505'; // адрес:порт прокси http://spys.one/proxys/US/ $prxy_auth = 'auth_user:auth_pass'; // логин:пароль для аутентификации curl_setopt_array ($ch, array(CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true)); /********************* Код для подключения к прокси *********************/ curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); // тип прокси curl_setopt($ch, CURLOPT_PROXY, $prxy); // ip, port прокси curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxy_auth); // авторизация на прокси curl_setopt($ch, CURLOPT_HEADER, false); // отключение передачи заголовков в запросе curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // возврат результата в качестве строки curl_setopt($ch, CURLOPT_POST, 1); // использование простого HTTP POST curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // отмена проверки сертификата удаленным сервером curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // Параметры ниже подходят, если у вас PHP свежей версии //curl_setopt($ch, CURLOPT_RESOLVE, array("api.telegram.org:443:146.185.158.29")); //curl_setopt($ch, CURLOPT_DNS_SERVERS, '8.8.8.8,8.8.4.4'); // тип прокси /***********************************************************************/ $response = curl_exec($ch); if ($response === FALSE) { $response = "cURL Error: " . curl_error($ch); } $info = curl_getinfo($ch); //$response .= ' Took ' . $info['total_time'] . ' seconds for url ' . $info['url']." "; curl_close($ch); return $response; } ?>
.current { margin-top: 10px; padding: 0 10px; font-size: 14px; } .current div { height: 100px; } .current b { display:inline-block; border: 3px solid #ffc700; padding: 5px; font-size: 30px; } .t2 b { border: 3px solid #3bd100; } .t3 b { border: 3px solid #ff0000; } .t4 b { border: 3px solid #ee00ff; } .current .time { padding-top: 20px; font-weight: bold; text-align: center; font-size: 18px; } .selectperiod { padding: 5px 30px; } .selectperiod div{ text-align:center; }
В архиве лежат еще файлики библиотеки jquery, moment.js и jquery.datetimepicker.
Для красивого отображения графика используется ChartJS.
Итак, теперь у нас сохраняются температуры в базу и даже выводятся в виде красивого графика. Дело за малым — настроить оповещения в Telegram, когда температуры достигают слишком малых значений. По этой инструкции легко настроить бот, который будет присылать вам сообщения, если послать запрос на api.telegram.org. Мой код такой обработки температур и отправки в телеграм в файле index.php:
// Если температуры ниже минимума, отправим сообщение в Telegram if($t4<40 || $t2<35 || $t1<50 || $t3<50) { include 'tele.php'; message_to_telegram("ГВС_П: $t4, ГВС_О: $t2, Котельная_П: $t1, Котельная_О: $t3"); }
Но вот незадача, телеграм то в России заблокирован! И у большинства провайдеров вы даже IP-адрес сервера не получите по этому имени домена.
Для работы с этим сервером придется использовать SOCKS-прокси (выше есть код файла tele.php), плюс в некоторых случаях потребуется прописать
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
чтобы запросы начали ходить бесперебойно.
Можно раскомментировать строчку 9 в файле tele.php и проверить отправку, обновив страницу.
Подготовка завершена, осталось разместить все это на местах:
Придерживаясь колхоз-стайла, датчики к трубе можно примотать великим Скотчем, но для лучшей теплопередачи предварительно смазать место контакта термопастой:
Для потомков и случайно оказавшихся в бойлерной слесарей желательно обозначить на корпусе, что это за фиговина с проводами:
Прокинули витую пару с интернетом до бойлерной, подключили роутер, вставили в розетку наше устройство и наслаждаемся картинкой:
Открывать ссылку можно с любого устройства, даже со слабым интернетом. Вообще говоря, веб-сервер можно поднять и на самой ESP8266 и рисовать аналогичный график, заморочившись с обновлением IP-адреса через DynDNS, но «я художник, я так вижу» :) Да и стабильность у такого варианта все-таки куда выше, а еще доработки в серверную часть можно вносить прямо с рабочего места в другом городе, а не программировать каждый раз плату.
На этом можно остановиться, ибо все работает, но есть и пока не решенные проблемы.
Первая — это датчики почему-то иногда сбоят. С периодичностью раз в 4 часа плата почему-то путается в показаниях и присылает откровенную фигню. Один раз даже показало температуру в -70 градусов на одном из датчиков, после чего я внес в серверный код условие добавления в базу значений, только если они все положительные.

Второе — если температура все-таки опустится ниже установленного в коде минимума, Telegram-бот вас задолбает сообщениями. Нужно сохранять время предыдущей отправки (например, в файле на хостинге или в базе) и анализировать, прошел ли час (к примеру).
Третье — найденный в интернете прокси-сервер может просто-напросто прекратить свое существование, его нужно будет сменить. А сообщения то вам не будут присылаться в телеграм… Поэтому надо добавить либо оповещение на email, либо сделать парсинг свежих прокси каждый раз. А может кто-то знает лучшее решение?
Надеюсь, хоть кому-нибудь данный пост был полезен, а может кого-то подтолкнет все-таки заказать эту плату и сделать свой вариант погодной станции (следуя поговорке «Что бы вы ни собрались сделать на базе ESP8266, все равно получится погодная станция»).
(c) 2017 Источник материала