С нами продолжают делиться решениями на основе модулей Мастер Кит:
«В качестве эксперимента решил я тут попробовать сделать некий прототип “умного дома” на скорую руку и с минимальными затратами. “Хотелок” оказалось много: и свет, и вентиляция, и окна, и вода, и ИК-управление электроприборами. На первых порах решил ограничиться минимумом задач: вентиляция и освещение в комнате.
За основу системы взял Arduino Uno, с возможностью управления четырьмя пинами независимо друг от друга, и несколько модулей беспроводного управления от Мастер Кит: роли исполнительных устройств взяли на себя одно- и двухканальные реле MP3328 и MP3330, а сигналы на них передаются с помощью восьмиканального передатчика MP3329 на частоте 433 МГц.
На MP3330 я повесил управление двумя светодиодными лентами над диваном, — уютная подсветка для вечернего чтения, — а на MP3328 — управление серво-машинкой для открывания / закрывания окна.
Конструкцию привода соорудил из подручных материалов, а именно, из деталей конструктора LEGO.
Ну и, разумеется, простого управления по радиоканалу мне было недостаточно: необходим был доступ ко всей системе через веб-интерфейс и мониторинг состояния в режиме реального времени.
В качестве ресурса хранения я взял стандартный MySQL, где я создал таблицу с айдишниками контроллеров, пинов и значением состояния каждого из них, в целях безопасности привязав к каждому общий идентификатор системы. Также, я жестко зашил возможность установки фиксированного таймера на изменение состояния, и под это тоже предусмотрено поле.
При работе с веб-сокетами Arduino слегка подглючивало, и разбираться в причинах пока времени не было, поэтому, набросал по-простому: ajax-запросами через каждые 100мс. Это, конечно, губительно для трафика (почти 30Мб в сутки), — но для квартиры с безлимитным интернетом на первое время хватит.
Простейший веб-интерфейс: 4 кнопки, по одной на каждый пин. Однократное нажатие изменяет состояние, а длительное нажатие устанавливает таймер (у меня пока жестко зашиты 3 минуты) на его изменение.
В итоге, упрощенная версия логики такова:
Веб-интерфейс обращается к серверу и проверяет состояние контроллеров, после чего, выводит интерфейс работы с системой: 4 кнопки в соответствующем состоянию виде:
$.ajax({ url: 'engine/ajax.php', type: 'POST', dataType: 'json', data: {action: 'getStates'}, }) .done(function(data) { for (key in data.success){ var controller = data.success[key]; if($('.btn#btn_' + controller.id).length === 0){ html = ''; html += '<div '; if (controller.timer_switch > 0){ var seconds = 60 - (controller.timer_switch % 60) html += 'seconds="' + seconds + '"'; } html += 'id="btn_' + controller.id + '" class="btn" state="' + controller.state + '"><div class="btn_timer"></div></div>'; $('#btn_placer').append(html); } else { if (controller.state == 1){ $('.btn#btn_' + controller.id).removeClass('btn_off').addClass('btn_on'); } else if (controller.state == 0){ $('.btn#btn_' + controller.id).removeClass('btn_on').addClass('btn_off'); } if (controller.timer_switch > 0){ var seconds = 60 - controller.timer_switch % 60; var minutes = Math.floor(controller.timer_switch / 60); $('.btn#btn_' + controller.id).addClass('seconds').css('background-position', (-seconds * 100) + 'px 0px').find('.btn_timer').text(minutes + 'M'); } else { $('.btn#btn_' + controller.id).css('background-position', '0px 0px').removeClass('seconds').find('.btn_timer').text(''); } } } setTimeout(function(){ getStates(); }, 1000); }) .fail(function(data) { console.log('error'); });
function getStates($sql){ $result = $sql->query("SELECT * FROM `controllers` WHERE `home_id` = '1' ORDER BY `order`"); if (isset($result->rows)){ $result = $result->rows; foreach ($result as $key => $value) { if (strtotime($result[$key]['timer']) > -62169990000){ // echo strtotime($result[$key]['timer']); $timer_switch = strtotime($result[$key]['timer']) - strtotime(date("Y-m-d H:i:s")); $result[$key]['timer_switch'] = $timer_switch; if ($timer_switch < 0){ $sql->query("UPDATE `controllers` SET `state` = '".$result[$key]['timer_state']."', `timer` = '0000-00-00 00:00:00', `timer_state` = '' WHERE `id` = '".$result[$key]['id']."'"); } } } $res['success'] = $result; } else { $res['error'] = 'Неверный пароль'; } return $res; }
При нажатии соответствующей кнопки происходит отправка POST-запроса к файлу ajax.php:
$(document).on('mousedown', '.btn', function(event){ event.preventDefault(); var id = parseInt($(this).attr('id').replace('btn_', '')); click_wait = false; mousetimer = setTimeout(function(){ click_wait = true; setTimer(id); }, 2000); }); $(document).on('mouseup', '.btn', function(){ clearTimeout(mousetimer); if (!click_wait){ var id = parseInt($(this).attr('id').replace('btn_', '')); switchController(id); console.log('click !!!'); click_wait = false; } }); function switchController(id){ var el = $('.btn#btn_' + id); var state = parseInt($(el).attr('state')); var need_state; if (state == 0){ need_state = 1; } else if (state == 1){ need_state = 0; } $(el).addClass('waiting'); $.ajax({ url: 'engine/ajax.php', type: 'POST', dataType: 'json', data: {action: 'setState', id: id, state: state, need_state: need_state}, }) .done(function(data) { if (data.success == 'ok'){ $(el).attr('state', need_state); $(el).removeClass('waiting').removeClass('btn_on').removeClass('btn_off'); if(need_state == 1){ $(el).addClass('btn_on'); } else if(need_state == 0){ $(el).addClass('btn_off'); } } }) .fail(function(data) { console.log('error'); }); } function setTimer(id){ var el = $('.btn#btn_' + id); var state = parseInt($(el).attr('state')); var need_state; if (state == 0){ need_state = 1; } else if (state == 1){ need_state = 0; } $(el).attr('seconds', 0); $(el).addClass('seconds'); $.ajax({ url: 'engine/ajax.php', type: 'POST', dataType: 'json', data: {action: 'setTimer', id: id, state: state, need_state: need_state}, }) .done(function(data) { }) .fail(function(data) { console.log('error'); }); }
В переменной id передаём айдишник контроллера, а в переменной state — значение его состояния. В файле ajax.php получаем POST-запрос и кладём новые данные в значения записи соответствующего id.
function setState($sql){ $need_state = (int)$_POST['need_state']; $state = (int)$_POST['state']; $id = (int)$_POST['id']; $result = $sql->query("UPDATE `controllers` SET `state` = '".$need_state."', `timer` = '0000-00-00 00:00:00' WHERE `id` = '".$id."'"); // Проверка выполенения Arduino $result = $sql->query("SELECT `state` FROM `controllers` WHERE `id` = '".$id."'"); if ($need_state == $result->row['state']){ $res['success'] = 'ok'; } else { $res['success'] = 'err'; } return $res; } function setTimer($sql){ $need_state = (int)$_POST['need_state']; $state = (int)$_POST['state']; $id = (int)$_POST['id']; $result = $sql->query("UPDATE `controllers` SET `timer` = NOW() + INTERVAL 3 MINUTE, `timer_state` = '".$need_state."' WHERE `id` = '".$id."'"); // Проверка выполенения Arduino $result = $sql->query("SELECT `state` FROM `controllers` WHERE `id` = '".$id."'"); if ($need_state == $result->row['state']){ $res['success'] = 'ok'; } else { $res['success'] = 'err'; } return $res; }
Arduino же, в свою очередь, по Ethernet обращается к веб-серверу, проверяет уникальный ключ системы, значения контроллеров, парсит полученную строку и переключает значения пинов.
if (isset($_GET['key']) && $_GET['key'] !== ''){ $key = $_GET['key']; } else { die; } $key = $sql->escape($key); $result = $sql->query("SELECT * FROM `controllers` WHERE `home_id` = '1' AND `key` = '".$key."' ORDER BY `order`"); foreach ($result->rows as $key => $value) { if ($value['id'] == '1'){ if ($value['state'] == '1'){ $ar .= 'Q'; } else if ($value['state'] == '0') { $ar .= 'q'; } } if ($value['id'] == '2'){ if ($value['state'] == '1'){ $ar .= 'W'; } else if ($value['state'] == '0') { $ar .= 'w'; } } if ($value['id'] == '3'){ if ($value['state'] == '1'){ $ar .= 'E'; } else if ($value['state'] == '0') { $ar .= 'e'; } } }
После чего, строка вида “%qUerTY” передается в скетч, где парсится, в зависимости от жестко зашитых правил: каждая буква соответствует своему номеру пина, а регистр отвечает за конечное значение: прописная — 1, строчная — 0.
void readData(){ if (led_connect){ digitalWrite(6, HIGH); } else { digitalWrite(6, LOW); } digitalWrite(4, LOW); previousMillis = currentMillis; led_connect = !led_connect; while (client.available()){ switch (char c = client.read()) { case 'Q': digitalWrite(9, HIGH); break; case 'q': digitalWrite(9, LOW); break; case 'W': digitalWrite(8, HIGH); break; case 'w': digitalWrite(8, LOW); break; case 'E': digitalWrite(7, HIGH); myservo.write(0); break; case 'e': digitalWrite(7, LOW); myservo.write(180); break; } } }
На пин, отвечающий за серво-машинку, можно передавать значения от 0 до 180 (градусов поворота), а, допустим, на диммер — до 255. Мне же пока хватает двух значений: 0 или 1.
С подключением модулей проблем не возникло: оба реле сопрягаются с передатчиком один раз, без необходимости повторной привязки, и прекрасно выполняют свою задачу.
А вот с мощностью серво-машинки я немного просчитался: взял на 6 кг, — чего, в общем, хватает для того, чтобы прикрывать и отпускать створку окна, что она открывалась под собственным весом, — но лучше брать что-то помощнее, на случай, сильного ветра или сквозняка.
Вообще, простор для творчества безграничен: к примеру, можно подключить датчики, например, CO2 или температуры, и открывать / закрывать окно при достижении определенных показателей.
Ну и всю систему, разумеется, я планирую переписать на веб-сокетах, облегчив запросы, — и сделать более гибкой передачу и парсинг значений параметров.
Небольшое видео
Работа подсветки
Открывание окна
Дмитрий Кузнецов»
ссылка на оригинал статьи http://geektimes.ru/post/261332/
Добавить комментарий