Продолжение «Умного дома» на базе Arduino.
![image](http://habrastorage.org/getpro/geektimes/post_images/de0/0a1/0c0/de00a10c0263ba85799c9e3781325a99.png)
Здравствуйте.
Для лучшего понимания, рекомендую почитать первую часть.
В этой части описывается плавное регулирование освещением (диммер, далее ШИМ), а так же сохранение значений в энергонезависимую память EEPROM.
Сохранение данных в память, даёт возможность вернуть работу системы к прежнему состоянию, после обесточивания.
Здесь можно посмотреть и потрогать в реальном времени.
Кнопки будут включать/отключать соответствующие пины, а двиганье ползунками будет увеличивать/уменьшать ШИМ на D5 и D6.
Внутри индикаторов расположены полукруглые кнопки с помощью которых можно мгновенно отключить и включить ШИМ. При включении вернётся то значение ШИМа, которое было при отключении.
Перейду сразу к делу…
Ардуино
Вначале обнулим EEPROM. Залейте этот скетч:
#include <EEPROM.h> void setup() { // write a 0 to all 512 bytes of the EEPROM for (int i = 0; i < 512; i++) EEPROM.write(i, 0); // turn the LED on when we're done digitalWrite(13, HIGH); } void loop() { }
#include <EEPROM.h> byte d2 = EEPROM.read(2); // флаги (состояние пинов) хранится в EEPROM, считываем их byte d3 = EEPROM.read(3); byte d4 = EEPROM.read(4); int shim1 = EEPROM.read(5); // значение ШИМ хранится в EEPROM, считываем их int shim2 = EEPROM.read(6); byte d11 = EEPROM.read(11); byte d12 = EEPROM.read(12); byte d13 = EEPROM.read(13); byte descript[5]; // массив void setup() { Serial.begin(57600); pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(11, OUTPUT); pinMode(12, OUTPUT); pinMode(13, OUTPUT); if(d2) digitalWrite(2, HIGH); else digitalWrite(2, LOW); // если до перезагрузки d2 была включена, то включаем, если нет, то нет delay(500); // чтобы не включалось всё сразу, делаем паузы if(d3) digitalWrite(3, HIGH); else digitalWrite(3, LOW); delay(500); if(d4) digitalWrite(4, HIGH); else digitalWrite(4, LOW); delay(500); analogWrite(5, shim1 * 2.55); // включаем ШИМ d5 delay(500); analogWrite(6, shim2 * 2.55); // включаем ШИМ d6 delay(500); if(d11) digitalWrite(11, HIGH); else digitalWrite(11, LOW); delay(500); if(d12) digitalWrite(12, HIGH); else digitalWrite(12, LOW); delay(500); if(d13) digitalWrite(13, HIGH); else digitalWrite(13, LOW); } void loop() { if (Serial.available()>4) // ждём дескриптор и нужный символ { if (Serial.read()=='Y') // проверяем первый символ, если это 'Y', то продолжаем принимать, если нет, то выходим из цикла чтения { for (byte i=0; i < 5; i++) { descript[i] = Serial.read(); // добавляем символы в массив } if((descript[0] =='+') && (descript[1] =='=') && (descript[2] =='Z')) // проверяем дескриптор { switch (descript[3]) { case 'o': // обновление glavnaia(); // отправка ответа break; case 'A': // d2 вкл digitalWrite(2, HIGH); // вкл d2 d2 = 1; // ставим флаг в единицу (вкл) EEPROM.write(2, d2); // записываем состояние d2 в ячейку №2 EEPROM glavnaia(); // отправка ответа break; case 'a': // d2 откл digitalWrite(2, LOW); // откл d2 d2 = 0; // ставим флаг в ноль (откл) EEPROM.write(2, d2); // записываем состояние d2 в ячейку №2 EEPROM glavnaia(); // отправка ответа break; case 'B': // d3 digitalWrite(3, HIGH); d3 = 1; EEPROM.write(3, d3); glavnaia(); break; case 'b': // d3 digitalWrite(3, LOW); d3 = 0; EEPROM.write(3, d3); glavnaia(); break; case 'C': // d4 digitalWrite(4, HIGH); d4 = 1; EEPROM.write(4, d4); glavnaia(); break; case 'c': // d4 digitalWrite(4, LOW); d4 = 0; EEPROM.write(4, d4); glavnaia(); break; case 'D': // d5 прибавляем shim1 shim1++; // прибавляем if(shim1 > 100) shim1 = 100; // если больше ста, то будет сто EEPROM.write(5, shim1); // записываем значение в ячейку №5 EEPROM analogWrite(5, shim1 * 2.55); // включаем ШИМ D5 glavnaia(); // функция отправки ответа break; case 'd': // d5 убавляем shim1 shim1--; if(shim1 < 1) shim1 = 0; EEPROM.write(5, shim1); analogWrite(5, shim1 * 2.55); glavnaia(); break; case 'E': // d6 прибавляем shim2 shim2++; if(shim2 > 100) shim2 = 100; EEPROM.write(6, shim2); analogWrite(6, shim2 * 2.55); glavnaia(); break; case 'e': // d6 убавляем shim2 shim2--; if(shim2 < 1) shim2 = 0; EEPROM.write(6, shim2); analogWrite(6, shim2 * 2.55); glavnaia(); break; case 'F': // мгновенное включение ШИМ на D5 shim1 = EEPROM.read(5); // считываем значение ШИМ из EEPROM analogWrite(5, shim1 * 2.55); // включаем ШИМ D5 glavnaia(); break; case 'f': // мгновенное отключение ШИМ на D5 shim1 = 0; analogWrite(5, shim1); // отключаем ШИМ D5, но НЕ записываем в EEPROM glavnaia(); break; case 'G': // мгновенное включение ШИМ на D6 shim2 = EEPROM.read(6); // считываем значение ШИМ из EEPROM analogWrite(6, shim2 * 2.55); // включаем ШИМ D6 glavnaia(); break; case 'g': // мгновенное отключение ШИМ на D6 shim2 = 0; analogWrite(6, shim2); // отключаем ШИМ D6, но НЕ записываем в EEPROM glavnaia(); break; case 'J': // d11 digitalWrite(11, HIGH); d11 = 1; EEPROM.write(11, d11); glavnaia(); break; case 'j': // d11 digitalWrite(11, LOW); d11 = 0; EEPROM.write(11, d11); glavnaia(); break; case 'K': // d12 digitalWrite(12, HIGH); d12 = 1; EEPROM.write(12, d12); glavnaia(); break; case 'k': // d12 digitalWrite(12, LOW); d12 = 0; EEPROM.write(12, d12); glavnaia(); break; case 'M': // d13 digitalWrite(13, HIGH); d13 = 1; EEPROM.write(13, d13); glavnaia(); break; case 'm': // d13 digitalWrite(13, LOW); d13 = 0; EEPROM.write(13, d13); glavnaia(); break; default: glavnaia(); } } else // если символ был не 'Y', то очищаем буфер { for(byte i=0; i < 255; i++) { Serial.read(); } } } // конец if (Serial.read()=='Y') } // конец чтение порта } // конец loop void glavnaia() // отправка данных { Serial.print(d2);//0 Serial.print(","); Serial.print(d3);//1 Serial.print(","); Serial.print(d4);//2 Serial.print(","); Serial.print(0);//3 // пока отключаем, потом пригодится Serial.print(","); Serial.print(0);//4 // пока отключаем, потом пригодится Serial.print(","); Serial.print(0);//5 // пока отключаем, потом пригодится Serial.print(","); Serial.print(0);//6 // пока отключаем, потом пригодится Serial.print(","); Serial.print(0);//7 // пока отключаем, потом пригодится Serial.print(","); Serial.print(0);//8 // пока отключаем, потом пригодится Serial.print(","); Serial.print(d11);//9 Serial.print(","); Serial.print(d12);//10 Serial.print(","); Serial.print(d13);//11 Serial.print(","); Serial.print(shim1); // 12 отсылается Serial.print(","); Serial.println(shim2); // 13 отсылается 14 значений разделённых запятой }
Обмен данными с ардуиной описан здесь или тут.
Как работает
Кнопки:
![image](http://istarik.ru/uploads/images/00/00/01/2015/05/13/cf85d9.png)
Кнопка (например D13) включит светодиод и запишет в EEPROM единицу. В веб-интерфейс отправится флаг 1 сообщающий о том, что команда выполнена. Кнопка подсветится.
При повторном нажатии, светодиод погаснет и в EEPROM запишется ноль. В веб-интерфейс отправится флаг 0. Кнопка поменяет цвет.
То есть в веб-интерфейсе отобразится только гарантированно выполненная команда.
... case 'M': // d13 digitalWrite(13, HIGH); // включили d13 = 1; // установили флаг EEPROM.write(13, d13); // записали его в память glavnaia(); // функция отправки ответа break; case 'm': // d13 digitalWrite(13, LOW); d13 = 0; EEPROM.write(13, d13); glavnaia(); break; ...
Если включить D13 и обесточить ардуину, то при последующем включении ардуина прочитает соответствующую ячейку памяти:
... byte d13 = EEPROM.read(13); ...
И если там была единица, то в блоке void setup() включит светодиод:
... delay(500); if(d13) digitalWrite(13, HIGH); else digitalWrite(13, LOW);
Пауза перед включением нужна для того, чтоб НЕ включались все потребители одновременно (например это дача, и ардуиной управляются обогреватели в разных комнатах).
Диммер:
![image](http://istarik.ru/uploads/images/00/00/01/2015/05/13/da6f2e.png)
Диапазон значений ШИМ от 0 до 255, ардуина получает (от клиента) значения в диапазоне от 0 до 100, которые внутри программы умножаются на 2.55 и выводятся на «ножку».
case 'D': // d5 прибавляем shim1 shim1++; // прибавили if(shim1 > 100) shim1 = 100; // если больше ста, то будет сто EEPROM.write(5, shim1); // записали значение в память analogWrite(5, shim1 * 2.55); // зажгли лампочку glavnaia(); // функция отправки ответа break; case 'd': // d5 убавляем shim1 shim1--; if(shim1 < 1) shim1 = 0; EEPROM.write(5, shim1); analogWrite(5, shim1 * 2.55); glavnaia(); break;
Если ползунок сдвигается, то в ардуину отсылается команда увеличить/уменьшить ШИМ на единицу (и так далее пока двигаете ползунок). Переменная shim1++; увеличивается, её значение помещается в память, и на пин подаётся shim1 умноженый на 2.55.
После этого значение shim1 отсылается обратно в веб-интерфейс и присваиваются индикатору и ползунку.
Индикатору и ползунку будет присвоено то значение, которое гарантированно выполнено ардуиной.
Если часть данных потеряется, ползунок сам отодвинется.
При нажатии на кнопку внутри индикатора:
![image](http://istarik.ru/uploads/images/00/00/01/2015/05/13/7e6624.png)
В ардуину отправится команда обнуляющая значение shim1
case 'F': // мгновенное включение ШИМ на D5 shim1 = EEPROM.read(5); // считываем значение ШИМ из EEPROM analogWrite(5, shim1 * 2.55); // включаем ШИМ D5 glavnaia(); break; case 'f': // мгновенное отключение ШИМ на D5 shim1 = 0; analogWrite(5, shim1); // отключаем ШИМ D5, но НЕ записываем в EEPROM glavnaia(); break;
При этом, в память ничего НЕ записывается, а нажатие на соседнюю кнопку вернёт значение из памяти.
(так удобнее отключать свет, нежели ползунок сдвигать)
Интерфейс
Скачайте архив и распакуйте его в рабочую папку сервера (по умолчанию это /var/www), как то так – /var/www/knopki_shimpolz (у вас может быть своя папка).
В браузере зайдите по адресу ваш_роутер/knopki_shimpolz/. Появится вот такая картинка:
![image](http://istarik.ru/uploads/images/00/00/01/2015/05/13/4bdfab.png)
Как это работает
Желательно открыть файл index.html из архива, и почитать комментарии.
Диммер:
При первой загрузки страницы, срабатывает функция обновления — show(); (в дальнейшем она работает с установленым интервалом) и у ардуины вместе с другими данными запрашиваются значения ШИМ:
/*обновление*/ show(); setInterval(show,2000); /* частота обновления в милисекундах */ function show(){ /* функция обновления */ if(flagobnov == 1) { /* это флаг нужен для временного отключения обновления */ $.ajax({ type: "GET", url: "box2.php?df=o", /* отправка символа о */ timeout:200, /* время (мс), в течении которого функция будет ждать ответа от сервера */ cache: false, success: function(data){ var vars = data.split(","); /* разбор строки принятой от ардуино */ if(vars.length == dlina){ /* проверка длины данных (количество блоков разделённых запятой) */ /*d2*/ if(vars[0] == 1) { $(".d2otkl").show(); $(".d2vkl").hide(); } /* в зависимости от принятого флага скрывает/показавыет кнопку вкл или откл */ else if(vars[0] == 0) { $(".d2otkl").hide(); $(".d2vkl").show(); } /*d3*/ if(vars[1] == 1) { $(".d3otkl").show(); $(".d3vkl").hide(); } else if(vars[1] == 0) { $(".d3otkl").hide(); $(".d3vkl").show(); } ... shim1 = vars[12]; /* получаем значение ШИМ */ sh1(); /* и выводим его на первый индикатор */ shim2 = vars[13]; sh2(); ...
После получения значения shim1, программа переходит в функцию sh1();
function sh1(){ /* рисование первого индикатора */ var $ppc = $('.progress-pie-chart'), percent = shim1, deg = 360*percent/100; if (percent > 50) { $ppc.addClass('gt-50'); } else $ppc.removeClass('gt-50'); $('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)'); $('.ppc-percents span').html(percent+' % D5 '); /* название на кнопке - D5 */ sl1(); }
Значение shim1 выводится на индикатор (зелёный кружок) и работа передаётся в функцию sl1();
Функция sl1(); устанавливает ползунок в соответствии со значением shim1
function sl1(){ /* первый слайдер */ $( "#slider" ).slider({ value : shim1, min : 0, max : 100, step : 1, slide: function( event, ui ) { ...
Функция slide: function( event, ui ) ожидает движения ползунка.
Когда ползунок будет сдвинут в ту или иную сторону на одно деление, сработает следующий алгоритм:
Отключается обновление ⇨
flagobnov = 0;
Проверяется в какую сторону сдвинут ползунок (в большую или меньшую) ⇨
if( ui.value > shim1 ){
else if( ui.value < shim1 ){
Ардуине отправляется символ указывающий увеличить (уменьшить) ШИМ на единицу ⇨
$.ajax({ type: "GET", url: "box2.php?df=D", /* говорим ардуине что надо увеличить ШИМ на единицу */
Получаем новое значение ШИМ от ардуины и вызывает функцию отрисовки индикатора (sh1();) с новым значением ⇨
shim1 = vars[12]; /* получаем от ардуины ответ с новым значением ШИМ */ sh1(); /* и выводим значение ШИМа на индикатор */
Включаем обновление ⇨
flagobnov = 1;
Функция (sh1();) в свою очередь отрисовывает индикатор и передаёт управление в функцию (sl1();).
Функция (sl1();) устанавливает ползунок в соответствии с новым значением ШИМ и ожидает очередного движение ползунка.
function sh1(){ /* рисование первого индикатора */ var $ppc = $('.progress-pie-chart'), percent = shim1, deg = 360*percent/100; if (percent > 50) { $ppc.addClass('gt-50'); } else $ppc.removeClass('gt-50'); $('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)'); $('.ppc-percents span').html(percent+' % D5 '); /* название на кнопке - D5 */ sl1(); } function sl1(){ /* первый слайдер */ $( "#slider" ).slider({ value : shim1, min : 0, max : 100, step : 1, slide: function( event, ui ) { /* отправили новое значение в арду, получили его обратно, отправили на индикатор и отдуда вернули сюда чтоб установить слайдер */ flagobnov = 0; /* пока таскаем ползунок отключаем обновление, чтоб не засорять "эфир" */ if( ui.value > shim1 ){ /* если потащили ползунок в большую сторону, то увеличиваем ШИМ */ $.ajax({ type: "GET", url: "box2.php?df=D", /* говорим ардуине что надо увеличить ШИМ на единицу */ timeout:200, cache: false, success: function(data){ var vars = data.split(","); if(vars.length == dlina) { shim1 = vars[12]; /* получаем от ардуины ответ с новым значением ШИМ */ sh1(); /* и выводим значение ШИМа на индикатор */ } } }); } else if( ui.value < shim1 ){ /* если потащили ползунок в меньшую сторону, то уменьшаем ШИМ */ $.ajax({ type: "GET", url: "box2.php?df=d", /* говорим ардуине что надо уменьшить ШИМ на единицу */ timeout:200, cache: false, success: function(data){ var vars = data.split(","); if(vars.length == dlina) { shim1 = vars[12]; /* получаем от ардуины ответ с новым значением ШИМ */ sh1(); /* и выводим значение ШИМа на индикатор */ } } }); } flagobnov = 1; /* включаем обновление */ } }); }
Значение индикатора и позиция ползунка, гарантированно соответствуют значению в ардуине.
Нажатие на кнопку «Мгновенное отключение ШИМ» отправляет в ардуину команду обнулить ШИМ, а кнопка «Мгновенное включение ШИМ» запросит у ардуины значение ШИМ, которое было до обнуления.
/* d5 ШИМ */ /*мгновенное включение ШИМ на D5*/ $(".d5shimvkl").click(function(){ $.ajax({ type: "GET", url: "box2.php?df=F", timeout:200, cache: false, success: function(data) { var vars = data.split(","); if(vars.length == dlina) { shim1 = vars[12]; /* получаем от ардуины ответ с новым значением ШИМ */ sh1(); /* и выводим значение ШИМа на индикатор */ } } }); return false; }); /*мгновенное отключение ШИМ на D5*/ $(".d5shimotkl").click(function(){ $.ajax({ type: "GET", url: "box2.php?df=f", timeout:200, cache: false, success: function(data) { var vars = data.split(","); if(vars.length == dlina) { shim1 = vars[12]; /* получаем от ардуины ответ с новым значением ШИМ */ sh1(); /* и выводим значение ШИМа на индикатор */ } } }); return false; });
Внешний вид
Позиция индикаторов, их цвет и шрифт меняется в файле shim.css
/* первый кружок */ .progress-pie-chart { width: 200px; height: 200px; top: 90px; /* позиция */ left: 80px; /* позиция */ border-radius: 50%; background-color: #E5E5E5; position: absolute; } ... .ppc-percents span { display: block; font-size: 26px; /*размер текста на кружках*/ font-weight: 600; /*ширина текста на кружках*/ font-family: Arial, Helvetica, sans-serif; /*шрифт*/ color: #161616; /*цвет текста на кружках*/ text-shadow: 0px 1px 2px #7c7c7c; /*цвет и размер тени текста*/ }
Размер и позицию ползунков можно менять в файле slai.css
.ui-slider { position: relative; width: 200px; /* ширина ползунка */ text-align: left; outline: none; } ... /* кнопка ползунка */ .ui-slider-horizontal .ui-slider-handle { width: 50px; /*размер кнопки ползунка*/ height: 50px; margin-left: -25px; outline: none; box-shadow: 0 0 10px 3px rgba(0,0,0,0.3); border-radius: 4px; border: 1px solid #2b2c2b; cursor: pointer; } ... /*первый ползунок*/ s1 { position: absolute; top: 360px; left: 80px; font-size: 26px; /*размер текста на кнопках*/ font-weight: 600; /*ширина текста на кнопках*/ font-family: Arial, Helvetica, sans-serif; /*шрифт*/ color: #161616; /*цвет текста на кнопках*/ text-shadow: 0px 1px 2px #7c7c7c; /*цвет и размер тени текста*/ } /*второй ползунок*/ s2 { position: absolute; top: 360px; left: 420px; font-size: 26px; /*размер текста на кнопках*/ font-weight: 600; /*ширина текста на кнопках*/ font-family: Arial, Helvetica, sans-serif; /*шрифт*/ color: #161616; /*цвет текста на кнопках*/ text-shadow: 0px 1px 2px #7c7c7c; /*цвет и размер тени текста*/ }
На этом всё, в следующей части будет рассмотренно подключение температурных датчиков и включение/отключение различных устройств по температуре, а так же добавлен спящий режим для веб-интерфейса.
Спасибо.
ссылка на оригинал статьи http://geektimes.ru/post/250362/
Добавить комментарий