Шаблон интерфейса для «умного дома» на Ардуино — вторая часть

от автора

Продолжение «Умного дома» на базе Arduino.

image

Здравствуйте.

Для лучшего понимания, рекомендую почитать первую часть.

В этой части описывается плавное регулирование освещением (диммер, далее ШИМ), а так же сохранение значений в энергонезависимую память EEPROM.

Сохранение данных в память, даёт возможность вернуть работу системы к прежнему состоянию, после обесточивания.

Здесь можно посмотреть и потрогать в реальном времени.

Видеосюжет прилагается

Управляется ipadом.

Кнопки будут включать/отключать соответствующие пины, а двиганье ползунками будет увеличивать/уменьшать ШИМ на 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

Кнопка (например 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

Диапазон значений ШИМ от 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

В ардуину отправится команда обнуляющая значение 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

Как это работает

Желательно открыть файл 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *