Продолжение «Умного дома» на базе 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 // если дескриптор ложный, то очищаем буфер
      {
        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);

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

Чтобы отключить «автоматическое» управление, например для пина d2, надо в начале кода переправить это:

...
byte d2 = EEPROM.read(2);
...


на это:

byte d2 = 0;


В void setup() убрать строки:

...
if(d2) digitalWrite(2, HIGH); else digitalWrite(2, LOW); 
delay(500);
...


И в блоке switch (descript[3]) закомментировать запись в EEPROM, вот так:

...
         ////////////// Кнопки ///////////////////
         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; 
...


Диммер:

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; /*цвет и размер тени текста*/
}


На этом всё, в следующей части будет рассмотрено подключение температурных датчиков и включение/отключение различных устройств по температуре, а также добавлен спящий режим для веб-интерфейса.

Спасибо.

Комментарии (43)


  1. Bluefox
    18.05.2015 12:17

    Может подключить к arduino mqtt протокол и рассказать здесь об этом?
    forum.iobroker.com/viewtopic.php?f=16&t=714&sid=186a2c748ed6196910ab6d580e4bb495&start=60#p5294

    Тогда можно будет сделать WebFront какой угодно. Например с такими контролами:



  1. ncix
    18.05.2015 19:49
    +2

    Ничего личного, но код просто ужасен.

    >>Serial.print()
    Раз 20 подряд. Нет ощущения, что можно как-то более изящно написать?

    >>switch (descript[3])
    Свитч на пару страниц…

    >>shim1, flagobnov, glavnaia, sh1, sl1, d5shimvkl
    Имена функций и переменных просто жесть (1С-ное прошлое?)

    >>analogWrite(5, shim1 * 2.55)
    Magic numbers?

    Хоть Ардуино и «детская» платформа, но это не значит что допустимо наплевательское отношение к качеству кода.


    1. dimastd Автор
      19.05.2015 05:15

      (>>Serial.print()
      Раз 20 подряд. Нет ощущения, что можно как-то более изящно написать?

      >>switch (descript[3])
      Свитч на пару страниц…)

      Вы правы, можно всё записать короче, но я преследовал максимальную читабельность и удобство изятия/вставки блоков.
      Для «новичков», на мой взгляд, так будет понятнее (в дальнейшем они сами научатся писать конечные автоматы).


      (>>shim1, flagobnov, glavnaia, sh1, sl1, d5shimvkl
      Имена функций и переменных просто жесть (1С-ное прошлое?))

      К 1с я не причастен, а что конкретно Вам не нравится в названиях?


      (Magic numbers?)

      В тексте есть пояснение:
      «Диапазон значений ШИМ от 0 до 255, ардуина получает (от клиента) значения в диапазоне от 0 до 100, которые внутри программы умножаются на 2.55 и выводятся на «ножку».»


      1. ncix
        19.05.2015 09:28
        +1

        >>Вы правы, можно всё записать короче, но я преследовал максимальную читабельность и удобство изятия/вставки блоков.
        Не понятно где тут читабельность, если свич приходится мотать туда-сюда две страницы. Стоило хотя бы вынести каждый case в отдельную процедуру. Кстати процедур получится в разы меньше чем количество case — в вашем коде на самом деле три или четыре варианта case'ов просто с разными номерами ножек. Так и просятся отдельные процедуры.

        >>К 1с я не причастен, а что конкретно Вам не нравится в названиях?
        Транслит, конечно. Это конечно не криминал, но моветон в любой программе. Язык программирования на базе английского — давайте правильные английские имена (ШИМ==PWM, flagobnov == renew_flag и т.п. ) И несодержательные имена sh1 и sl1.

        >> которые внутри программы умножаются на 2.55
        По-хорошему, нужно объявить:
        #define PWM_MULTIPLIER 2.55; //пояснение

        >>и выводятся на «ножку».»
        Все ножки тоже стоит объявить константами (или #define'ами). Так во-первых читабельность повысится

        #define PWM_D5 5;

        analogWrite(PWM_D5, shim1 * PWM_MULTIPLIER); // включаем ШИМ D5

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

        >>Для «новичков», на мой взгляд, так будет понятнее (в дальнейшем они сами научатся писать конечные автоматы).
        Новичок просто стащит ваш код в свой проект. И налепит сверху еще менее симпатичные вещи.


        1. dimastd Автор
          19.05.2015 10:48

          (вынести каждый case в отдельную процедуру.)

          Если Вас не затруднит, приведите пример.

          (Все ножки тоже стоит объявить константами)

          Это само собой, в обозримом будущем.


          1. ncix
            19.05.2015 11:12

            Как-то так:

            void setOutput(uint8_t pin, uint8_t state) //процедура управления ножкой № pin
            {
            digitalWrite(pin, state); // вкл d2
            d2 = 1; // ставим флаг в единицу (вкл)
            EEPROM.write(pin, d2); // записываем состояние d2 в ячейку №pin EEPROM
            glavnaia(); // отправка ответа
            }
            void pwmAdd(int & pwm, uint8_t pin) //Увеличение ШИМ на ножке pin
            {
            pwm++; // прибавляем
            if(pwm > 100) pwm = 100; // если больше ста, то будет сто
            EEPROM.write(pin, pwm); // записываем значение в ячейку №5 EEPROM
            analogWrite(pin, pwm * 2.55); // включаем ШИМ D5
            glavnaia(); // функция отправки ответа
            break;
            }
            void pwmDec(int & pwm, uint8_t pin) //Уменьшение ШИМ на ножке pin
            {
            pwm--;
            if(pwm < 1) pwm = 0;
            EEPROM.write(pin, pwm);
            analogWrite(pin, pwm * 2.55);
            glavnaia();
            break;
            }

            case 'A': // d2 вкл
            setOutput(2, HIGH);
            break;

            case 'a': // d2 откл
            setOutput(2, LOW);
            break;
            case 'B': // d3 вкл
            setOutput(3, HIGH)
            break;
            case 'b': // d3 откл
            setOutput(3, LOW)
            break;
            и т.д.

            case 'D': // d5 прибавляем shim1
            pwmAdd(shim1, 5);
            break;
            case 'd': // d5 убавляем shim1
            pwmDec(shim1, 5);
            break;

            case 'E': // d6 прибавляем shim2
            pwmAdd(shim2, 6);
            break;
            case 'd': // d5 убавляем shim1
            pwmDec(shim2, 6);
            break;
            и т.п.

            Если идти дальше, то стоило бы задать массивы управляющих символов и номеров портов и вообще избавится от switch. Или класс порта сделать, инкапсулирующий всю его логику управления.
            Потому как если кто-то будет делать то же самое на Arduino Mega, например, там из-за количества портов портянка кода будет совсем уже неприличной длины.


            1. dimastd Автор
              19.05.2015 11:33

              Клёво! ) У меня в «своих» проектах реализовано почти так же, только код довольно таки путаный и переменных лишних понатыкано (поэтому и решил выложить «простыню»).
              У Вас «чистенько» получилось. Спасибо.

              Может тогда и по этому вопросу вразумите? Если конечно не затруднит.

              (то стоило бы задать массивы управляющих символов и номеров портов и вообще избавится от switch. Или класс порта сделать, инкапсулирующий всю его логику управления. )


              1. ncix
                19.05.2015 16:31

                Сел писать на скорую руку, выходит не очень. А по-хорошему нужно больше времени и понимания дальнейшей судьбы проекта. Не хотите расшарить исходники на гитхабе? Может я или кто-нибудь еще порефакторили бы.


                1. dimastd Автор
                  19.05.2015 16:50

                  Сделаю, на днях.


  1. doom369
    18.05.2015 20:34

    Зачем, если есть Blynk?


    1. dimastd Автор
      19.05.2015 05:25

      Зачем Blynk, если есть я? ) Да вообще, зачем писать программы, если уже есть похожие?


      1. doom369
        19.05.2015 10:01

        Ну, вы же не стали писать свой язык чтобы написать эту прогу… Тут тоже самое. Если решение решает вашу задачу, то почему бы им не воспользоватся?


    1. ncix
      19.05.2015 09:34

      Для обучения, очевидно. Автор молодец, что сделал все это сам, а не поюзал готовый проект. Фреймворковый подход плохо работает на контроллере с несколькими килобайтами флеш-памяти.


      1. doom369
        19.05.2015 10:02

        Фреймворковый подход отлично работает, особенно когда либа занимает не много.


        1. dimastd Автор
          19.05.2015 11:08

          (Если решение решает вашу задачу, то почему бы им не воспользоватся?)

          Увы, Blynk не решает моей задачи.

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


          1. doom369
            19.05.2015 11:17

            Ну так Блинк токо пример. Есть 100-500 готовых клаудов которые делают то же + бесплатно + для любых железок… Гугл в помощь «iot cloud».

            В плане обучения — ок, но как решение, оно уже позади мира на 5 лет.


            1. dimastd Автор
              19.05.2015 11:44

              Благодарствую, а не подскажите нечто русскоязычное?


              1. dimastd Автор
                19.05.2015 11:52

                … Или что-то конкретное.

                Из тех решений, с которыми я знаком, достаточно много недоработок. При этом подобные вещи позиционируются как законченый продукт.


                1. doom369
                  19.05.2015 13:13

                  Вот www.kaaproject.org например опенсорсная разработка. Правда на яве. Вроде как даже от украинских разработчиков. Их реально сотни и я не слежу за ними и в них явно меньше недоработок чем у Вас =), не в обиду. Уверен что вы сможете нагуглить и русскоязычные, если это критично.


                  1. dimastd Автор
                    19.05.2015 13:41

                    Спасибо.


                  1. Bluefox
                    23.05.2015 19:23

                    Их 14. Что то я у них не нашёл FrontEnd.


  1. ignat99
    24.05.2015 23:44

    github.com/souliss/souliss
    www.souliss.net

    А зачем вы изобретаете велосипед souliss?


    1. dimastd Автор
      25.05.2015 01:52

      Я это делал три года назад, была нужна управляемая теплица, загородом. Сейчас решил выложить всё, что делал, поэтапно. Может кому-то пригодится.


      1. dimastd Автор
        25.05.2015 02:48

        Например: Теплица расположена в 150 км от города.

        Интернет сомнительного качества (gsm-модем).

        МК управляющий всем, подключён к автономному источнику питания. Основной способ общения с устройством через интернет, резервный — отдельный gsm-модуль.

        В определённое время включаются лампы (ДНАТ 600W — потребляет 1200W ), таких шесть штук. Включаться должны с интервалом в пять минут, если все вместе включать, то могут не разжечься.

        Лампы нагревают помещение — включается вытяжка, температура падает — вытяжка отключается.

        Лампы отключаются — через час включается душ (на несколько секунд). Температура падает, включаются радиаторы и поддерживают температуру.
        Время от времени подаётся СО2, из балона.

        Земля подсыхает — включается полив.

        Отключают электроэнергию, отваливается интернет или ещё что, тогда по GSM приходит оповещение и можно смсками общаться с устройством.

        Включают электроэнергию — система должна прийти в рабочее состояние, в частности включить лампы поочереди или обогреватели.

        Если появился дым, то включается душ и шлётся смс.


        Всем этим хозяиством (настройки времени включения, полив, душ, СО2, температура и т.д.) нужно управлять через веб-морду, так как пользовались этим, люди далёкие от программирования.

        Вряд ли бы нашлось готовое решение с подобным функционалом.


        1. dimastd Автор
          25.05.2015 03:07

          И да, забыл пока писал, спасибо за ссылку.


          1. ignat99
            25.05.2015 07:13

            В Souliss три основных момента:
            1. Есть прямо в EEPROM таблица с типами команд и смещениями на слот (итем или вхождения)
            github.com/souliss/souliss/wiki/Typicals
            2. Само конфигурирующаяся одноранговая сеть.
            github.com/souliss/souliss/wiki/Supported%20Network%20Architecture
            3. Зрелый Интерфейс в приложении для Андроид.

            Так же есть возможность запуска быстрых (через короткие интервалы времени — доли секудны) и медленных (часы) заданий.


            1. dimastd Автор
              25.05.2015 15:05

              Я поизучал вчера, получается работает только с андройдом?
              А Вы имееете к этому причастие? Просто некоторые вещи непонятны, и хотелось спросить.


              1. ignat99
                25.05.2015 17:47

                Вобщем то не только, можно сделать веб сервер или поставить управление на плату с Ардвино. IMHO


                1. dimastd Автор
                  25.05.2015 18:16

                  Не совсем понял, что Вы хотели сказать.


                  1. ignat99
                    25.05.2015 19:12

                    github.com/souliss/souliss/wiki/ZozOt%20OpenEnergyMonitor

                    Если у вас под рукой нет старого разбитого мобильного с Андроид без работающиго экрана и без 3G но c WiFi, или Olimex устройства с Android, то можно поставить приложение на Анроид TV или да же на виртуальную машину.

                    habrahabr.ru/company/boxowerview/blog/179463


                    1. dimastd Автор
                      25.05.2015 19:32

                      У меня, к сожалению, нет ни одного устройства на Андройд. (и честно говоря мне не понятно, зачем привязываться к конкретной ОС) Интернет-шилда тоже нет. Можно ли, как-то без андройда и инет-шилда попробовать?

                      Есть айпад, роутер и ардуина (в ближайшее время приедет ESP8266).

                      Кстати, по ссылке www.souliss.net, для работы с модулем ESP8266, предлагается модифицированная IDE Arduino, но только для Win.


                      1. ignat99
                        25.05.2015 19:44

                        Я сейчас прошиваю модули под GNU/Linux Ubuntu. Всё прекрасно работает. По этой ссылке можно взять Arduino IDE под любую платформу.

                        Библиотеку для ESP8266 в стиле Arduino для Souliss можно взять тут. Она под Olimex ESP8266-EVB, где реле управляет GPIO5.

                        Можно без андроид устройства — установите среду для работы с Android. В среде есть симулятор Анроид, на нём можно запустить приложение.


                        1. dimastd Автор
                          25.05.2015 20:21

                          установите среду для работы с Android. В среде есть симулятор Анроид


                          А как быть тем, у кого нет андройда?

                          Установлена последняя IDE, компилятся любые примеры из библиотеки, кроме Hello_ESP8266.

                          e08_Hello_ESP8266.ino:23:25: fatal error: ESP8266WiFi.h: No such file or directory
                          compilation terminated.
                          Ошибка компиляции.
                          



                          1. ignat99
                            25.05.2015 21:18

                            This is the procedure from the Arduino/ESP8266 Github
                            • Install Arduino 1.6.4 from the Arduino website.
                            • Start Arduino and open Perferences window.
                            • Enter http://arduino.esp8266.com/package_esp8266com_index.json into Additional Board Manager URLs field. You can add multiple URLs, separating them with commas.
                            • Open Boards Manager from Tools > Board menu and install esp8266 platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
                            Once done, install manually (not from the library manager) souliss friariello-porting
                            github.com/souliss/souliss/archive/friariello-porting.zip

                            Открыть Preferences и в поле Additional Board Manager URLs ввести:
                            arduino.esp8266.com/package_esp8266com_index.json

                            Далее пойти в Tools -> Boards Manager -> Board

                            Подождать пока обновятся ссылки, и где то в конце появиться библиотека для ESP8266.
                            Нажать на ссылку Install для этой библиотеки.

                            Далее снова Tools -> Board и выбрать уже из установленных либо ESP8266 генерик либо MOD-WIFI-ESP8266(-DEV)

                            После чего всё должно собраться.


                            1. dimastd Автор
                              25.05.2015 22:54

                              Спасибо.


                              1. ignat99
                                25.05.2015 23:43

                                groups.google.com/forum/#!topic/souliss-es/bMNkxVCfb1c

                                Вот ещё программа для генерации скетчей. Возможно не совместима до конца с friariello-porting (потребуется правка руками). Я её не смотрел, но народу нравиться.


                                1. dimastd Автор
                                  26.05.2015 00:09

                                  Спасибо. Из предыдущего поста заработало, буду ждать модуль. Когда придёт, попробую ради любопытства, андройд возьму у соседа.


                                  1. ignat99
                                    26.05.2015 00:32

                                    Если будут проблемы с Андроид приложением, Надо пойти в Аппликейшин Менеджер, Выбрать нужную программу, и нажать кнопку очистку данных. После запуска Souliss снова покажет окно с информацией по релизам.

                                    Длее в Опции. В Опциях выставить IP адрес вашего ESP8266. Далее в настройки базы данных. Удалить старую базу данных и сканировать ноды. Будет окно с IP адресом ESP8266, нажать OK.
                                    Напишет что обнаружена нода 0. Ну и появиться в разделе Node управляющие элементы вашего модуля.

                                    Так же я добавил Пулл реквест с обновлённым примером. Сделал управление светом с локальной кнопки вместе с удалённой кнопкой и обновление состояния.


                                    1. dimastd Автор
                                      26.05.2015 01:22

                                      Как происходит обмен данными? У меня следующим образом:
                                      Нажал на кнопку на html-странице (страничка ждёт ответа) ? полетел символ (например А — включить d13) на сервер ? сервер передал его php-файлу, php-файл приделал к символу дескриптор 'Y+=Z', получилось 'Y+=ZА' и отправил эту строку ардуине (и тоже ждёт ответа) ? ардуина выполнила команду и отправила ответ ? ответ вернулся по цепочке обратно в html-страничку и она отобразила состояние.

                                      Каждую секунду (регулируется) у ардуины запрашивается состояние всей переферии. Если что-то включили/отключили с одного устройства, то максимум через секунду это отобразится на других.


                                      1. ignat99
                                        26.05.2015 02:07

                                        Читайте вики в проекте:
                                        github.com/souliss/souliss/wiki/Data%20Structure

                                        Там всего 43 странички.


                                        1. ignat99
                                          26.05.2015 07:52

                                          github.com/sparkfun/BMP180_Breakout/tree/master/Libraries/Arduino/src

                                          Вот только что пофиксили I2C для сенсора давления. Акселерометр и Гироскоп — MPU6050 на очереди. MOD-IO2 от Olimex то же будут работать. Получается цена решения на 1 управляющий модуль в районе 15-20 евро.


                                          1. ignat99
                                            26.05.2015 08:19

                                            Надо изменить Wire.begin() надо дёргать не 4 и 5. Нужны GPIO2 и GPIO4 ( 6 и 5 UEXT I2C на ESP8266-EVB). Надо использовать Wire.begin(SDA_pin,SCL_pin), тут:
                                            github.com/sparkfun/BMP180_Breakout/blob/master/Libraries/Arduino/src/SFE_BMP180.cpp#L38
                                            и тут:
                                            github.com/kriswiner/MPU-6050/blob/master/MPU6050BasicExample.ino#L204


                                            1. ignat99
                                              27.05.2015 14:43

                                              Подробнее, что бы не пугать geektimes скетчем для MOD-BMP085 и приложенной библиотекой:

                                              forum.katera.ru/index.php?/topic/48974-platforma-esp8266-dlia-katerov-i-iakht/page-2