Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266


Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

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

Наткнулся тут на обзоры всяких датчиков от 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 руб

Сначала параллельно соединяем все датчики, которые нам нужны, в одно целое

по такой схеме:
Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266
Получилось примерно так:
Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

Потом, конечно, это все тщательно замотано изолентой/термоусадкой.

Подключаем к пинам по схеме, плату подсоединяем USB-кабелем к компьютеру, запускаем свежеустановленный Arduino 1.8.2 и настраиваем его, как описано тут. Порт меняем на COM4 (если плата уже подключена).

Распаковываем архив в папку, где у вашей Arduino лежат скетчи. В комплекте уже есть библиотеки, которые было так тяжело найти подходящие, пол дня убил на это.

Загружаем сам скетч sketch_esp8266dallas в среду разработки.

Меняем там на строках 14 и 15 название WiFi сети и пароль к ней, а также на 45 строке адрес сервера, на котором будут сохраняться передаваемые параметры. Серверная часть (PHP) описана ниже.

К слову, мы могли бы уже здесь отправлять уведомления в Telegram, если температуры ниже заданного уровня, немного изменив код отправки GET-запроса, но Telegram у нас в России под запретом, поэтому существуют некоторые проблемы с использованием его API. Альтернативный вариант — использовать API от sms.ru, но это уже совсем другая история. Главное, что сюда можно вписать абсолютно любое обращение к какому угодно сервису, будь то «народный мониторинг» или любой другой.

Пробуем скомпилировать скетч через меню «Скетч — Проверить/Компилировать», если все хорошо и ошибок нет, то можно загружать в плату «Скетч — Загрузка».

Открываем меню «Инструменты — Монитор порта» и видим примерно такую картину:

Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

А если у вас картина другая и плата 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-скриптов:

index.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>
json.php
<?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 );
?>
tele.php
<?

// сюда нужно вписать токен вашего бота
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;
}
?>
style.css
.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 и проверить отправку, обновив страницу.

Подготовка завершена, осталось разместить все это на местах:

Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

Придерживаясь колхоз-стайла, датчики к трубе можно примотать великим Скотчем, но для лучшей теплопередачи предварительно смазать место контакта термопастой:

Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

Для потомков и случайно оказавшихся в бойлерной слесарей желательно обозначить на корпусе, что это за фиговина с проводами:

Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

Прокинули витую пару с интернетом до бойлерной, подключили роутер, вставили в розетку наше устройство и наслаждаемся картинкой:

Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

Открывать ссылку можно с любого устройства, даже со слабым интернетом. Вообще говоря, веб-сервер можно поднять и на самой ESP8266 и рисовать аналогичный график, заморочившись с обновлением IP-адреса через DynDNS, но «я художник, я так вижу» 🙂 Да и стабильность у такого варианта все-таки куда выше, а еще доработки в серверную часть можно вносить прямо с рабочего места в другом городе, а не программировать каждый раз плату.

На этом можно остановиться, ибо все работает, но есть и пока не решенные проблемы.

Первая — это датчики почему-то иногда сбоят. С периодичностью раз в 4 часа плата почему-то путается в показаниях и присылает откровенную фигню. Один раз даже показало температуру в -70 градусов на одном из датчиков, после чего я внес в серверный код условие добавления в базу значений, только если они все положительные.

Вот пример полученных корявых данных
Контроль температур горячей воды в ТСЖ с оповещениями в Telegram на базе ESP8266

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

Третье — найденный в интернете прокси-сервер может просто-напросто прекратить свое существование, его нужно будет сменить. А сообщения то вам не будут присылаться в телеграм… Поэтому надо добавить либо оповещение на email, либо сделать парсинг свежих прокси каждый раз. А может кто-то знает лучшее решение?

Надеюсь, хоть кому-нибудь данный пост был полезен, а может кого-то подтолкнет все-таки заказать эту плату и сделать свой вариант погодной станции (следуя поговорке «Что бы вы ни собрались сделать на базе ESP8266, все равно получится погодная станция»).

Оцените статью